/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 3.0 Module
 * -------------------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Clipping tests.
 *//*--------------------------------------------------------------------*/

#include "es3fClippingTests.hpp"
#include "tcuRenderTarget.hpp"
#include "tcuTextureUtil.hpp"
#include "tcuImageCompare.hpp"
#include "tcuVectorUtil.hpp"
#include "deStringUtil.hpp"
#include "deRandom.hpp"

#include "sglrReferenceContext.hpp"
#include "sglrGLContext.hpp"

#include "glwEnums.hpp"
#include "glwDefs.hpp"
#include "glwFunctions.hpp"

using namespace glw; // GLint and other GL types

namespace deqp
{
namespace gles3
{
namespace Functional
{
namespace
{

using tcu::PixelBufferAccess;
using tcu::ConstPixelBufferAccess;
using tcu::TestLog;

static const tcu::Vec4	MASK_COLOR_OK			 = tcu::Vec4(0.0f, 0.1f, 0.0f, 1.0f);
static const tcu::Vec4	MASK_COLOR_DEV			 = tcu::Vec4(0.8f, 0.5f, 0.0f, 1.0f);
static const tcu::Vec4	MASK_COLOR_FAIL			 = tcu::Vec4(1.0f, 0.0f, 1.0f, 1.0f);

const int					TEST_CANVAS_SIZE  = 200;
const rr::WindowRectangle	VIEWPORT_WHOLE		(0,						0,					TEST_CANVAS_SIZE,		TEST_CANVAS_SIZE);
const rr::WindowRectangle	VIEWPORT_CENTER		(TEST_CANVAS_SIZE/4,	TEST_CANVAS_SIZE/4,	TEST_CANVAS_SIZE/2,		TEST_CANVAS_SIZE/2);
const rr::WindowRectangle	VIEWPORT_CORNER		(TEST_CANVAS_SIZE/2,	TEST_CANVAS_SIZE/2,	TEST_CANVAS_SIZE/2,		TEST_CANVAS_SIZE/2);


const char* shaderSourceVertex =	"#version 300 es\n"
									"in highp vec4 a_position;\n"
									"in highp vec4 a_color;\n"
									"in highp float a_pointSize;\n"
									"out highp vec4 varFragColor;\n"
									"void main (void)\n"
									"{\n"
									"	gl_Position = a_position;\n"
									"	gl_PointSize = a_pointSize;\n"
									"	varFragColor = a_color;\n"
									"}\n";
const char* shaderSourceFragment =	"#version 300 es\n"
									"layout(location = 0) out mediump vec4 fragColor;"
									"in highp vec4 varFragColor;\n"
									"void main (void)\n"
									"{\n"
									"	fragColor = varFragColor;\n"
									"}\n";

inline bool isBlack (const tcu::IVec4& a)
{
	return a.x() == 0 && a.y() == 0 && a.z() == 0;
}

inline bool isHalfFilled (const tcu::IVec4& a)
{
	const tcu::IVec4 halfFilled	(127, 0, 0, 0);
	const tcu::IVec4 threshold	(20, 256, 256, 256);

	return tcu::boolAll(tcu::lessThanEqual(tcu::abs(a - halfFilled), threshold));
}

inline bool isLessThanHalfFilled (const tcu::IVec4& a)
{
	const int halfFilled = 127;
	const int threshold	 = 20;

	return a.x() + threshold < halfFilled;
}

inline bool compareBlackNonBlackPixels (const tcu::IVec4& a, const tcu::IVec4& b)
{
	return isBlack(a) == isBlack(b);
}

inline bool compareColoredPixels (const tcu::IVec4& a, const tcu::IVec4& b)
{
	const bool aIsBlack = isBlack(a);
	const bool bIsBlack = isBlack(b);
	const tcu::IVec4 threshold(20, 20, 20, 0);

	if (aIsBlack && bIsBlack)
		return true;
	if (aIsBlack != bIsBlack)
		return false;

	return tcu::boolAll(tcu::lessThanEqual(tcu::abs(a - b), threshold));
}

void blitImageOnBlackSurface(const ConstPixelBufferAccess& src, const PixelBufferAccess& dst)
{
	const int			height	= src.getHeight();
	const int			width	= src.getWidth();

	for (int y = 0; y < height; y++)
	for (int x = 0; x < width; x++)
	{
		const tcu::IVec4 cSrc = src.getPixelInt(x, y);
		const tcu::IVec4 cDst = tcu::IVec4(cSrc.x(), cSrc.y(), cSrc.z(), 255);

		dst.setPixel(cDst, x, y);
	}
}

/*--------------------------------------------------------------------*//*!
 * \brief Pixelwise comparison of two images.
 * \note copied & modified from glsRasterizationTests
 *
 * Kernel radius defines maximum allowed distance. If radius is 0, only
 * perfect match is allowed. Radius of 1 gives a 3x3 kernel. Pixels are
 * equal if pixelCmp returns true..
 *
 * Return values:  -1 = Perfect match
 *					0 = Deviation within kernel
 *				   >0 = Number of faulty pixels
 *//*--------------------------------------------------------------------*/
inline int compareImages (tcu::TestLog& log, const ConstPixelBufferAccess& test, const ConstPixelBufferAccess& ref, const PixelBufferAccess& diffMask, int kernelRadius, bool (*pixelCmp)(const tcu::IVec4& a, const tcu::IVec4& b))
{
	const int			height				= test.getHeight();
	const int			width				= test.getWidth();
	int					deviatingPixels		= 0;
	int					faultyPixels		= 0;
	int					compareFailed		= -1;

	tcu::clear(diffMask, MASK_COLOR_OK);

	for (int y = 0; y < height; y++)
	{
		for (int x = 0; x < width; x++)
		{
			const tcu::IVec4 cRef	= ref.getPixelInt(x, y);
			const tcu::IVec4 cTest	= test.getPixelInt(x, y);

			// Pixelwise match, no deviation or fault
			if ((*pixelCmp)(cRef, cTest))
				continue;

			// Deviation
			{
				const int radius	= kernelRadius;
				bool foundRef		= false;
				bool foundTest		= false;

				// edges are considered a "deviation" too. The suitable pixel could be "behind" the edge
				if (y < radius || x < radius || y + radius >= height || x + radius >= width)
				{
					foundRef	= true;
					foundTest	= true;
				}
				else
				{
					// find ref
					for (int kY = y - radius; kY <= y + radius; kY++)
					for (int kX = x - radius; kX <= x + radius; kX++)
					{
						if ((*pixelCmp)(cRef, test.getPixelInt(kX, kY)))
						{
							foundRef = true;
							break;
						}
					}

					// find result
					for (int kY = y - radius; kY <= y + radius; kY++)
					for (int kX = x - radius; kX <= x + radius; kX++)
					{
						if ((*pixelCmp)(cTest, ref.getPixelInt(kX, kY)))
						{
							foundTest = true;
							break;
						}
					}
				}

				// A pixel is deviating if the reference color is found inside the kernel and (~= every pixel reference draws must be drawn by the gl too)
				// the result color is found in the reference image inside the kernel         (~= every pixel gl draws must be drawn by the reference too)
				if (foundRef && foundTest)
				{
					diffMask.setPixel(MASK_COLOR_DEV, x, y);
					if (compareFailed == -1)
						compareFailed = 0;
					deviatingPixels++;
					continue;
				}
			}

			diffMask.setPixel(MASK_COLOR_FAIL, x, y);
			faultyPixels++;									// The pixel is faulty if the color is not found
			compareFailed = 1;
		}
	}

	log << TestLog::Message << deviatingPixels	<< " deviating pixel(s) found." << TestLog::EndMessage;
	log << TestLog::Message << faultyPixels		<< " faulty pixel(s) found." << TestLog::EndMessage;

	return (compareFailed == 1 ? faultyPixels : compareFailed);
}

/*--------------------------------------------------------------------*//*!
 * \brief Pixelwise comparison of two images.
 *
 * Kernel radius defines maximum allowed distance. If radius is 0, only
 * perfect match is allowed. Radius of 1 gives a 3x3 kernel. Pixels are
 * equal if they both are black, or both are non-black.
 *
 * Return values:  -1 = Perfect match
 *					0 = Deviation within kernel
 *				   >0 = Number of faulty pixels
 *//*--------------------------------------------------------------------*/
int compareBlackNonBlackImages (tcu::TestLog& log, const ConstPixelBufferAccess& test, const ConstPixelBufferAccess& ref, const PixelBufferAccess& diffMask, int kernelRadius)
{
	return compareImages(log, test, ref, diffMask, kernelRadius, compareBlackNonBlackPixels);
}

/*--------------------------------------------------------------------*//*!
 * \brief Pixelwise comparison of two images.
 *
 * Kernel radius defines maximum allowed distance. If radius is 0, only
 * perfect match is allowed. Radius of 1 gives a 3x3 kernel. Pixels are
 * equal if they both are black, or both are non-black with color values
 * close to each other.
 *
 * Return values:  -1 = Perfect match
 *					0 = Deviation within kernel
 *				   >0 = Number of faulty pixels
 *//*--------------------------------------------------------------------*/
int compareColoredImages (tcu::TestLog& log, const ConstPixelBufferAccess& test, const ConstPixelBufferAccess& ref, const PixelBufferAccess& diffMask, int kernelRadius)
{
	return compareImages(log, test, ref, diffMask, kernelRadius, compareColoredPixels);
}

/*--------------------------------------------------------------------*//*!
 * \brief Overdraw check verification
 *
 * Check that image does not have at any point a
 * pixel with red component value > 0.5
 *
 * Return values:  false = area not filled, or leaking
 *//*--------------------------------------------------------------------*/
bool checkHalfFilledImageOverdraw (tcu::TestLog& log, const tcu::RenderTarget& m_renderTarget, const ConstPixelBufferAccess& image, const PixelBufferAccess& output)
{
	const int			height				= image.getHeight();
	const int			width				= image.getWidth();

	bool				faulty				= false;

	tcu::clear(output, MASK_COLOR_OK);

	for (int y = 0; y < height; y++)
	{
		for (int x = 0; x < width; x++)
		{
			const tcu::IVec4	cTest	= image.getPixelInt(x, y);

			const bool pixelValid = isBlack(cTest) || isHalfFilled(cTest) || (m_renderTarget.getNumSamples() > 1 && isLessThanHalfFilled(cTest));

			if (!pixelValid)
			{
				output.setPixel(MASK_COLOR_FAIL, x, y);
				faulty = true;
			}
		}
	}

	if (faulty)
		log << TestLog::Message << "Faulty pixel(s) found." << TestLog::EndMessage;

	return !faulty;
}

void checkPointSize (const glw::Functions& gl, float pointSize)
{
	GLfloat pointSizeRange[2] = {0,0};
	gl.getFloatv(GL_ALIASED_POINT_SIZE_RANGE, pointSizeRange);
	if (pointSizeRange[1] < pointSize)
		throw tcu::NotSupportedError("Maximum point size is too low for this test");
}

void checkLineWidth (const glw::Functions& gl, float lineWidth)
{
	GLfloat lineWidthRange[2] = {0,0};
	gl.getFloatv(GL_ALIASED_LINE_WIDTH_RANGE, lineWidthRange);
	if (lineWidthRange[1] < lineWidth)
		throw tcu::NotSupportedError("Maximum line width is too low for this test");
}

tcu::Vec3 IVec3ToVec3 (const tcu::IVec3& v)
{
	return tcu::Vec3((float)v.x(), (float)v.y(), (float)v.z());
}

bool pointOnTriangle (const tcu::IVec3& p, const tcu::IVec3& t0, const tcu::IVec3& t1, const tcu::IVec3& t2)
{
	// Must be on the plane
	const tcu::IVec3 n = tcu::cross(t1 - t0, t2 - t0);
	const tcu::IVec3 d = (p - t0);

	if (tcu::dot(n, d))
		return false;

	// Must be within the triangle area
	if (deSign32(tcu::dot(n, tcu::cross(t1 - t0, p - t0))) == deSign32(tcu::dot(n, tcu::cross(t2 - t0, p - t0))))
		return false;
	if (deSign32(tcu::dot(n, tcu::cross(t2 - t1, p - t1))) == deSign32(tcu::dot(n, tcu::cross(t0 - t1, p - t1))))
		return false;
	if (deSign32(tcu::dot(n, tcu::cross(t0 - t2, p - t2))) == deSign32(tcu::dot(n, tcu::cross(t1 - t2, p - t2))))
		return false;

	return true;
}

bool pointsOnLine (const tcu::IVec2& t0, const tcu::IVec2& t1, const tcu::IVec2& t2)
{
	return (t1 - t0).x() * (t2 - t0).y() - (t2 - t0).x() * (t1 - t0).y() == 0;
}

// returns true for cases where polygon is (almost) along xz or yz planes (normal.z < 0.1)
// \note[jarkko] Doesn't have to be accurate, just to detect some obviously bad cases
bool twoPointClippedTriangleInvisible(const tcu::Vec3& p, const tcu::IVec3& dir1, const tcu::IVec3& dir2)
{
	// fixed-point-like coords
	const deInt64					fixedScale	= 64;
	const deInt64					farValue	= 1024;
	const tcu::Vector<deInt64, 3>	d1			= tcu::Vector<deInt64, 3>(dir1.x(), dir1.y(), dir1.z());
	const tcu::Vector<deInt64, 3>	d2			= tcu::Vector<deInt64, 3>(dir2.x(), dir2.y(), dir2.z());
	const tcu::Vector<deInt64, 3>	pfixed		= tcu::Vector<deInt64, 3>(deFloorFloatToInt32(p.x() * fixedScale), deFloorFloatToInt32(p.y() * fixedScale), deFloorFloatToInt32(p.z() * fixedScale));
	const tcu::Vector<deInt64, 3>	normalDir	= tcu::cross(d1*farValue - pfixed, d2*farValue - pfixed);
	const deInt64					normalLen2	= tcu::lengthSquared(normalDir);

	return (normalDir.z() * normalDir.z() - normalLen2/100) < 0;
}

std::string genClippingPointInfoString(const tcu::Vec4& p)
{
	std::ostringstream msg;

	if (p.x() < -p.w())		msg << "\t(-X clip)";
	if (p.x() >  p.w())		msg << "\t(+X clip)";
	if (p.y() < -p.w())		msg << "\t(-Y clip)";
	if (p.y() >  p.w())		msg << "\t(+Y clip)";
	if (p.z() < -p.w())		msg << "\t(-Z clip)";
	if (p.z() >  p.w())		msg << "\t(+Z clip)";

	return msg.str();
}

std::string genColorString(const tcu::Vec4& p)
{
	const tcu::Vec4 white	(1.0f, 1.0f, 1.0f, 1.0f);
	const tcu::Vec4 red		(1.0f, 0.0f, 0.0f, 1.0f);
	const tcu::Vec4 yellow	(1.0f, 1.0f, 0.0f, 1.0f);
	const tcu::Vec4 blue	(0.0f, 0.0f, 1.0f, 1.0f);

	if (p == white)		return "(white)";
	if (p == red)		return "(red)";
	if (p == yellow)	return "(yellow)";
	if (p == blue)		return "(blue)";
	return "";
}

class PositionColorShader : public sglr::ShaderProgram
{
public:
	enum
	{
		VARYINGLOC_COLOR = 0
	};

			PositionColorShader (void);

	void	shadeVertices		(const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const;
	void	shadeFragments		(rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const;
};

PositionColorShader::PositionColorShader (void)
	: sglr::ShaderProgram(sglr::pdec::ShaderProgramDeclaration()
							<< sglr::pdec::VertexAttribute("a_position", rr::GENERICVECTYPE_FLOAT)
							<< sglr::pdec::VertexAttribute("a_color", rr::GENERICVECTYPE_FLOAT)
							<< sglr::pdec::VertexAttribute("a_pointSize", rr::GENERICVECTYPE_FLOAT)
							<< sglr::pdec::VertexToFragmentVarying(rr::GENERICVECTYPE_FLOAT)
							<< sglr::pdec::FragmentOutput(rr::GENERICVECTYPE_FLOAT)
							<< sglr::pdec::VertexSource(shaderSourceVertex)
							<< sglr::pdec::FragmentSource(shaderSourceFragment))
{
}

void PositionColorShader::shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
{
	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
	{
		const int positionAttrLoc = 0;
		const int colorAttrLoc = 1;
		const int pointSizeAttrLoc = 2;

		rr::VertexPacket& packet = *packets[packetNdx];

		// Transform to position
		packet.position = rr::readVertexAttribFloat(inputs[positionAttrLoc], packet.instanceNdx, packet.vertexNdx);

		// output point size
		packet.pointSize = rr::readVertexAttribFloat(inputs[pointSizeAttrLoc], packet.instanceNdx, packet.vertexNdx).x();

		// Pass color to FS
		packet.outputs[VARYINGLOC_COLOR] = rr::readVertexAttribFloat(inputs[colorAttrLoc], packet.instanceNdx, packet.vertexNdx);
	}
}

void PositionColorShader::shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
{
	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
	{
		rr::FragmentPacket& packet = packets[packetNdx];

		for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
			rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, rr::readVarying<float>(packet, context, VARYINGLOC_COLOR, fragNdx));
	}
}

class RenderTestCase : public TestCase
{
public:
					RenderTestCase	(Context& context, const char* name, const char* description);

	virtual void	testRender		(void) = DE_NULL;
	virtual void	init			(void) { }

	IterateResult	iterate			(void);
};

RenderTestCase::RenderTestCase (Context& context, const char* name, const char* description)
	: TestCase	(context, name, description)
{
}

RenderTestCase::IterateResult RenderTestCase::iterate (void)
{
	const int width	 = m_context.getRenderTarget().getWidth();
	const int height = m_context.getRenderTarget().getHeight();

	m_testCtx.getLog() << TestLog::Message << "Render target size: " << width << "x" << height << TestLog::EndMessage;
	if (width < TEST_CANVAS_SIZE || height < TEST_CANVAS_SIZE)
		throw tcu::NotSupportedError(std::string("Render target size must be at least ") + de::toString(TEST_CANVAS_SIZE) + "x" + de::toString(TEST_CANVAS_SIZE));

	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); // success by default
	testRender();

	return STOP;
}

class PointCase : public RenderTestCase
{
public:
									PointCase	(Context& context, const char* name, const char* description, const tcu::Vec4* pointsBegin, const tcu::Vec4* pointsEnd, float pointSize, const rr::WindowRectangle& viewport);

	void							init		(void);
	void							testRender	(void);

private:
	const std::vector<tcu::Vec4>	m_points;
	const float						m_pointSize;
	const rr::WindowRectangle		m_viewport;
};

PointCase::PointCase (Context& context, const char* name, const char* description, const tcu::Vec4* pointsBegin, const tcu::Vec4* pointsEnd, float pointSize, const rr::WindowRectangle& viewport)
	: RenderTestCase(context, name, description)
	, m_points		(pointsBegin, pointsEnd)
	, m_pointSize	(pointSize)
	, m_viewport	(viewport)
{
}

void PointCase::init (void)
{
	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
	checkPointSize (gl, m_pointSize);
}

void PointCase::testRender (void)
{
	using tcu::TestLog;

	const int numSamples			= de::max(m_context.getRenderTarget().getNumSamples(), 1);

	tcu::TestLog&					log			= m_testCtx.getLog();
	sglr::GLContext					glesContext	(m_context.getRenderContext(), log, 0, tcu::IVec4(0, 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE));
	sglr::ReferenceContextLimits	limits;
	sglr::ReferenceContextBuffers	buffers		(m_context.getRenderTarget().getPixelFormat(), m_context.getRenderTarget().getDepthBits(), 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE, numSamples);
	sglr::ReferenceContext			refContext	(limits, buffers.getColorbuffer(), buffers.getDepthbuffer(), buffers.getStencilbuffer());
	PositionColorShader				program;
	tcu::Surface					testSurface	(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
	tcu::Surface					refSurface	(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
	sglr::Context*					contexts[2] = {&glesContext, &refContext};
	tcu::Surface*					surfaces[2] = {&testSurface, &refSurface};

	// log the purpose of the test
	log << TestLog::Message << "Viewport: left=" << m_viewport.left << "\tbottom=" << m_viewport.bottom << "\twidth=" << m_viewport.width << "\theight=" << m_viewport.height << TestLog::EndMessage;
	log << TestLog::Message << "Rendering points with point size " << m_pointSize << ". Coordinates:" << TestLog::EndMessage;
	for (size_t ndx = 0; ndx < m_points.size(); ++ndx)
		log << TestLog::Message
				<< "\tx=" << m_points[ndx].x()
				<< "\ty=" << m_points[ndx].y()
				<< "\tz=" << m_points[ndx].z()
				<< "\tw=" << m_points[ndx].w()
				<< "\t" << genClippingPointInfoString(m_points[ndx])
				<< TestLog::EndMessage;

	for (int contextNdx = 0; contextNdx < 2; ++contextNdx)
	{
		sglr::Context&	ctx				= *contexts[contextNdx];
		tcu::Surface&	dstSurface		= *surfaces[contextNdx];
		const deUint32	programId		= ctx.createProgram(&program);
		const GLint		positionLoc		= ctx.getAttribLocation(programId, "a_position");
		const GLint		pointSizeLoc	= ctx.getAttribLocation(programId, "a_pointSize");
		const GLint		colorLoc		= ctx.getAttribLocation(programId, "a_color");

		ctx.clearColor					(0, 0, 0, 1);
		ctx.clearDepthf					(1.0f);
		ctx.clear						(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
		ctx.viewport					(m_viewport.left, m_viewport.bottom, m_viewport.width, m_viewport.height);
		ctx.useProgram					(programId);
		ctx.enableVertexAttribArray		(positionLoc);
		ctx.vertexAttribPointer			(positionLoc, 4, GL_FLOAT, GL_FALSE, 0, &m_points[0]);
		ctx.vertexAttrib1f				(pointSizeLoc, m_pointSize);
		ctx.vertexAttrib4f				(colorLoc, 1.0f, 1.0f, 1.0f, 1.0f);
		ctx.drawArrays					(GL_POINTS, 0, (glw::GLsizei)m_points.size());
		ctx.disableVertexAttribArray	(positionLoc);
		ctx.useProgram					(0);
		ctx.deleteProgram				(programId);
		ctx.finish						();

		ctx.readPixels(dstSurface, 0, 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
	}

	// do the comparison
	{
		tcu::Surface		diffMask		(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
		const int			kernelRadius	= 1;
		int					faultyPixels;

		log << TestLog::Message << "Comparing images... " << TestLog::EndMessage;
		log << TestLog::Message << "Deviation within radius of " << kernelRadius << " is allowed." << TestLog::EndMessage;

		faultyPixels = compareBlackNonBlackImages(log, testSurface.getAccess(), refSurface.getAccess(), diffMask.getAccess(), kernelRadius);

		if (faultyPixels > 0)
		{
			log << TestLog::ImageSet("Images", "Image comparison")
				<< TestLog::Image("TestImage", "Test image", testSurface.getAccess())
				<< TestLog::Image("ReferenceImage", "Reference image", refSurface.getAccess())
				<< TestLog::Image("DifferenceMask", "Difference mask", diffMask.getAccess())
				<< TestLog::EndImageSet
				<< tcu::TestLog::Message << "Got " << faultyPixels << " faulty pixel(s)." << tcu::TestLog::EndMessage;

			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got faulty pixels");
		}
	}
}

class LineRenderTestCase : public RenderTestCase
{
public:
	struct ColoredLineData
	{
		tcu::Vec4 p0;
		tcu::Vec4 c0;
		tcu::Vec4 p1;
		tcu::Vec4 c1;
	};

	struct ColorlessLineData
	{
		tcu::Vec4 p0;
		tcu::Vec4 p1;
	};
										LineRenderTestCase		(Context& context, const char* name, const char* description, const ColoredLineData*   linesBegin, const ColoredLineData*   linesEnd, float lineWidth, const rr::WindowRectangle& viewport);
										LineRenderTestCase		(Context& context, const char* name, const char* description, const ColorlessLineData* linesBegin, const ColorlessLineData* linesEnd, float lineWidth, const rr::WindowRectangle& viewport);

	virtual void						verifyImage				(const tcu::ConstPixelBufferAccess& testImageAccess, const tcu::ConstPixelBufferAccess& referenceImageAccess) = DE_NULL;
	void								init					(void);
	void								testRender				(void);

protected:
	const float							m_lineWidth;

private:
	std::vector<ColoredLineData>		convertToColoredLines	(const ColorlessLineData* linesBegin, const ColorlessLineData* linesEnd);

	const std::vector<ColoredLineData>	m_lines;
	const rr::WindowRectangle			m_viewport;
};

LineRenderTestCase::LineRenderTestCase (Context& context, const char* name, const char* description, const ColoredLineData* linesBegin, const ColoredLineData* linesEnd, float lineWidth, const rr::WindowRectangle& viewport)
	: RenderTestCase	(context, name, description)
	, m_lineWidth		(lineWidth)
	, m_lines			(linesBegin, linesEnd)
	, m_viewport		(viewport)
{
}

LineRenderTestCase::LineRenderTestCase (Context& context, const char* name, const char* description, const ColorlessLineData* linesBegin, const ColorlessLineData* linesEnd, float lineWidth, const rr::WindowRectangle& viewport)
	: RenderTestCase	(context, name, description)
	, m_lineWidth		(lineWidth)
	, m_lines			(convertToColoredLines(linesBegin, linesEnd))
	, m_viewport		(viewport)
{
}

void LineRenderTestCase::init (void)
{
	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
	checkLineWidth (gl, m_lineWidth);
}

void LineRenderTestCase::testRender (void)
{
	using tcu::TestLog;

	const int numSamples			= de::max(m_context.getRenderTarget().getNumSamples(), 1);
	const int verticesPerLine		= 2;

	tcu::TestLog&					log			= m_testCtx.getLog();
	sglr::GLContext					glesContext	(m_context.getRenderContext(), log, 0, tcu::IVec4(0, 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE));
	sglr::ReferenceContextLimits	limits;
	sglr::ReferenceContextBuffers	buffers		(m_context.getRenderTarget().getPixelFormat(), m_context.getRenderTarget().getDepthBits(), 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE, numSamples);
	sglr::ReferenceContext			refContext	(limits, buffers.getColorbuffer(), buffers.getDepthbuffer(), buffers.getStencilbuffer());
	PositionColorShader				program;
	tcu::Surface					testSurface	(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
	tcu::Surface					refSurface	(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
	sglr::Context*					contexts[2] = {&glesContext, &refContext};
	tcu::Surface*					surfaces[2] = {&testSurface, &refSurface};

	// log the purpose of the test
	log << TestLog::Message << "Viewport: left=" << m_viewport.left << "\tbottom=" << m_viewport.bottom << "\twidth=" << m_viewport.width << "\theight=" << m_viewport.height << TestLog::EndMessage;
	log << TestLog::Message << "Rendering lines with line width " << m_lineWidth << ". Coordinates:" << TestLog::EndMessage;
	for (size_t ndx = 0; ndx < m_lines.size(); ++ndx)
	{
		const std::string fromProperties = genClippingPointInfoString(m_lines[ndx].p0);
		const std::string toProperties   = genClippingPointInfoString(m_lines[ndx].p1);

		log << TestLog::Message << "\tfrom (x=" << m_lines[ndx].p0.x() << "\ty=" << m_lines[ndx].p0.y() << "\tz=" << m_lines[ndx].p0.z() << "\tw=" << m_lines[ndx].p0.w() << ")\t" << fromProperties << TestLog::EndMessage;
		log << TestLog::Message << "\tto   (x=" << m_lines[ndx].p1.x() << "\ty=" << m_lines[ndx].p1.y() << "\tz=" << m_lines[ndx].p1.z() << "\tw=" << m_lines[ndx].p1.w() << ")\t" << toProperties   << TestLog::EndMessage;
		log << TestLog::Message << TestLog::EndMessage;
	}

	// render test image
	for (int contextNdx = 0; contextNdx < 2; ++contextNdx)
	{
		sglr::Context&	ctx				= *contexts[contextNdx];
		tcu::Surface&	dstSurface		= *surfaces[contextNdx];
		const deUint32	programId		= ctx.createProgram(&program);
		const GLint		positionLoc		= ctx.getAttribLocation(programId, "a_position");
		const GLint		colorLoc		= ctx.getAttribLocation(programId, "a_color");

		ctx.clearColor					(0, 0, 0, 1);
		ctx.clearDepthf					(1.0f);
		ctx.clear						(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
		ctx.viewport					(m_viewport.left, m_viewport.bottom, m_viewport.width, m_viewport.height);
		ctx.useProgram					(programId);
		ctx.enableVertexAttribArray		(positionLoc);
		ctx.enableVertexAttribArray		(colorLoc);
		ctx.vertexAttribPointer			(positionLoc,	4, GL_FLOAT, GL_FALSE, sizeof(GLfloat[8]), &m_lines[0].p0);
		ctx.vertexAttribPointer			(colorLoc,		4, GL_FLOAT, GL_FALSE, sizeof(GLfloat[8]), &m_lines[0].c0);
		ctx.lineWidth					(m_lineWidth);
		ctx.drawArrays					(GL_LINES, 0, verticesPerLine * (glw::GLsizei)m_lines.size());
		ctx.disableVertexAttribArray	(positionLoc);
		ctx.disableVertexAttribArray	(colorLoc);
		ctx.useProgram					(0);
		ctx.deleteProgram				(programId);
		ctx.finish						();

		ctx.readPixels(dstSurface, 0, 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
	}

	// compare
	verifyImage(testSurface.getAccess(), refSurface.getAccess());
}

std::vector<LineRenderTestCase::ColoredLineData> LineRenderTestCase::convertToColoredLines(const ColorlessLineData* linesBegin, const ColorlessLineData* linesEnd)
{
	std::vector<ColoredLineData> ret;

	for (const ColorlessLineData* it = linesBegin; it != linesEnd; ++it)
	{
		ColoredLineData r;

		r.p0 = (*it).p0;
		r.c0 = tcu::Vec4(1, 1, 1, 1);
		r.p1 = (*it).p1;
		r.c1 = tcu::Vec4(1, 1, 1, 1);

		ret.push_back(r);
	}

	return ret;
}

class LineCase : public LineRenderTestCase
{
public:
				LineCase			(Context& context, const char* name, const char* description, const LineRenderTestCase::ColorlessLineData* linesBegin, const LineRenderTestCase::ColorlessLineData* linesEnd, float lineWidth, const rr::WindowRectangle& viewport, int searchKernelSize = 1);

	void		verifyImage			(const tcu::ConstPixelBufferAccess& testImageAccess, const tcu::ConstPixelBufferAccess& referenceImageAccess);

private:
	const int	m_searchKernelSize;
};

LineCase::LineCase (Context& context, const char* name, const char* description, const LineRenderTestCase::ColorlessLineData* linesBegin, const LineRenderTestCase::ColorlessLineData* linesEnd, float lineWidth, const rr::WindowRectangle& viewport, int searchKernelSize)
	: LineRenderTestCase	(context, name, description, linesBegin, linesEnd, lineWidth, viewport)
	, m_searchKernelSize	(searchKernelSize)
{
}

void LineCase::verifyImage (const tcu::ConstPixelBufferAccess& testImageAccess, const tcu::ConstPixelBufferAccess& referenceImageAccess)
{
	const int		faultyLimit		= 6;
	int				faultyPixels;

	const bool		isMsaa			= m_context.getRenderTarget().getNumSamples() > 1;
	tcu::TestLog&	log				= m_testCtx.getLog();
	tcu::Surface	diffMask		(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);

	log << TestLog::Message << "Comparing images... " << TestLog::EndMessage;
	log << TestLog::Message << "Deviation within radius of " << m_searchKernelSize << " is allowed." << TestLog::EndMessage;
	log << TestLog::Message << faultyLimit << " faulty pixels are allowed." << TestLog::EndMessage;

	faultyPixels = compareBlackNonBlackImages(log, testImageAccess, referenceImageAccess, diffMask.getAccess(), m_searchKernelSize);

	if (faultyPixels > faultyLimit)
	{
		log << TestLog::ImageSet("Images", "Image comparison")
			<< TestLog::Image("TestImage", "Test image", testImageAccess)
			<< TestLog::Image("ReferenceImage", "Reference image", referenceImageAccess)
			<< TestLog::Image("DifferenceMask", "Difference mask", diffMask.getAccess())
			<< TestLog::EndImageSet
			<< tcu::TestLog::Message << "Got " << faultyPixels << " faulty pixel(s)." << tcu::TestLog::EndMessage;

		if (m_lineWidth != 1.0f && isMsaa)
		{
			log << TestLog::Message << "Wide line support is optional, reporting compatibility warning." << TestLog::EndMessage;
			m_testCtx.setTestResult(QP_TEST_RESULT_COMPATIBILITY_WARNING, "Wide line clipping failed");
		}
		else
			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got faulty pixels");
	}
}

class ColoredLineCase : public LineRenderTestCase
{
public:
	ColoredLineCase (Context& context, const char* name, const char* description, const LineRenderTestCase::ColoredLineData* linesBegin, const LineRenderTestCase::ColoredLineData* linesEnd, float lineWidth, const rr::WindowRectangle& viewport);

	void verifyImage (const tcu::ConstPixelBufferAccess& testImageAccess, const tcu::ConstPixelBufferAccess& referenceImageAccess);
};

ColoredLineCase::ColoredLineCase (Context& context, const char* name, const char* description, const LineRenderTestCase::ColoredLineData* linesBegin, const LineRenderTestCase::ColoredLineData* linesEnd, float lineWidth, const rr::WindowRectangle& viewport)
	: LineRenderTestCase (context, name, description, linesBegin, linesEnd, lineWidth, viewport)
{
}

void ColoredLineCase::verifyImage (const tcu::ConstPixelBufferAccess& testImageAccess, const tcu::ConstPixelBufferAccess& referenceImageAccess)
{
	const bool		msaa	= m_context.getRenderTarget().getNumSamples() > 1;
	tcu::TestLog&	log		= m_testCtx.getLog();

	if (!msaa)
	{
		const int	kernelRadius	= 1;
		const int	faultyLimit		= 6;
		int			faultyPixels;

		tcu::Surface diffMask(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);

		log << TestLog::Message << "Comparing images... " << TestLog::EndMessage;
		log << TestLog::Message << "Deviation within radius of " << kernelRadius << " is allowed." << TestLog::EndMessage;
		log << TestLog::Message << faultyLimit << " faulty pixels are allowed." << TestLog::EndMessage;

		faultyPixels = compareColoredImages(log, testImageAccess, referenceImageAccess, diffMask.getAccess(), kernelRadius);

		if (faultyPixels > faultyLimit)
		{
			log << TestLog::ImageSet("Images", "Image comparison")
				<< TestLog::Image("TestImage", "Test image", testImageAccess)
				<< TestLog::Image("ReferenceImage", "Reference image", referenceImageAccess)
				<< TestLog::Image("DifferenceMask", "Difference mask", diffMask.getAccess())
				<< TestLog::EndImageSet
				<< tcu::TestLog::Message << "Got " << faultyPixels << " faulty pixel(s)." << tcu::TestLog::EndMessage;

			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got faulty pixels");
		}
	}
	else
	{
		const float threshold = 0.3f;
		if (!tcu::fuzzyCompare(log, "Images", "", referenceImageAccess, testImageAccess, threshold, tcu::COMPARE_LOG_ON_ERROR))
		{
			if (m_lineWidth != 1.0f)
			{
				log << TestLog::Message << "Wide line support is optional, reporting compatibility warning." << TestLog::EndMessage;
				m_testCtx.setTestResult(QP_TEST_RESULT_COMPATIBILITY_WARNING, "Wide line clipping failed");
			}
			else
				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got faulty pixels");
		}
	}
}

class TriangleCaseBase : public RenderTestCase
{
public:
	struct TriangleData
	{
		tcu::Vec4 p0;
		tcu::Vec4 c0;
		tcu::Vec4 p1;
		tcu::Vec4 c1;
		tcu::Vec4 p2;
		tcu::Vec4 c2;
	};

										TriangleCaseBase	(Context& context, const char* name, const char* description, const TriangleData* polysBegin, const TriangleData* polysEnd, const rr::WindowRectangle& viewport);

	virtual void						verifyImage			(const tcu::ConstPixelBufferAccess& testImageAccess, const tcu::ConstPixelBufferAccess& referenceImageAccess) = DE_NULL;
	void								testRender			(void);

private:
	const std::vector<TriangleData>		m_polys;
	const rr::WindowRectangle			m_viewport;
};

TriangleCaseBase::TriangleCaseBase (Context& context, const char* name, const char* description, const TriangleData* polysBegin, const TriangleData* polysEnd, const rr::WindowRectangle& viewport)
	: RenderTestCase(context, name, description)
	, m_polys		(polysBegin, polysEnd)
	, m_viewport	(viewport)
{
}

void TriangleCaseBase::testRender (void)
{
	using tcu::TestLog;

	const int numSamples			= de::max(m_context.getRenderTarget().getNumSamples(), 1);
	const int verticesPerTriangle	= 3;

	tcu::TestLog&					log			= m_testCtx.getLog();
	sglr::GLContext					glesContext	(m_context.getRenderContext(), log, 0, tcu::IVec4(0, 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE));
	sglr::ReferenceContextLimits	limits;
	sglr::ReferenceContextBuffers	buffers		(m_context.getRenderTarget().getPixelFormat(), m_context.getRenderTarget().getDepthBits(), 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE, numSamples);
	sglr::ReferenceContext			refContext	(limits, buffers.getColorbuffer(), buffers.getDepthbuffer(), buffers.getStencilbuffer());
	PositionColorShader				program;
	tcu::Surface					testSurface	(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
	tcu::Surface					refSurface	(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
	sglr::Context*					contexts[2] = {&glesContext, &refContext};
	tcu::Surface*					surfaces[2] = {&testSurface, &refSurface};

	// log the purpose of the test
	log << TestLog::Message << "Viewport: left=" << m_viewport.left << "\tbottom=" << m_viewport.bottom << "\twidth=" << m_viewport.width << "\theight=" << m_viewport.height << TestLog::EndMessage;
	log << TestLog::Message << "Rendering triangles. Coordinates:" << TestLog::EndMessage;
	for (size_t ndx = 0; ndx < m_polys.size(); ++ndx)
	{
		const std::string v0Properties = genClippingPointInfoString(m_polys[ndx].p0);
		const std::string v1Properties = genClippingPointInfoString(m_polys[ndx].p1);
		const std::string v2Properties = genClippingPointInfoString(m_polys[ndx].p2);
		const std::string c0Properties = genColorString(m_polys[ndx].c0);
		const std::string c1Properties = genColorString(m_polys[ndx].c1);
		const std::string c2Properties = genColorString(m_polys[ndx].c2);

		log << TestLog::Message << "\tv0 (x=" << m_polys[ndx].p0.x() << "\ty=" << m_polys[ndx].p0.y() << "\tz=" << m_polys[ndx].p0.z() << "\tw=" << m_polys[ndx].p0.w() << ")\t" << v0Properties << "\t" << c0Properties << TestLog::EndMessage;
		log << TestLog::Message << "\tv1 (x=" << m_polys[ndx].p1.x() << "\ty=" << m_polys[ndx].p1.y() << "\tz=" << m_polys[ndx].p1.z() << "\tw=" << m_polys[ndx].p1.w() << ")\t" << v1Properties << "\t" << c1Properties << TestLog::EndMessage;
		log << TestLog::Message << "\tv2 (x=" << m_polys[ndx].p2.x() << "\ty=" << m_polys[ndx].p2.y() << "\tz=" << m_polys[ndx].p2.z() << "\tw=" << m_polys[ndx].p2.w() << ")\t" << v2Properties << "\t" << c2Properties << TestLog::EndMessage;
		log << TestLog::Message << TestLog::EndMessage;
	}

	// render test image
	for (int contextNdx = 0; contextNdx < 2; ++contextNdx)
	{
		sglr::Context&	ctx				= *contexts[contextNdx];
		tcu::Surface&	dstSurface		= *surfaces[contextNdx];
		const deUint32	programId		= ctx.createProgram(&program);
		const GLint		positionLoc		= ctx.getAttribLocation(programId, "a_position");
		const GLint		colorLoc		= ctx.getAttribLocation(programId, "a_color");

		ctx.clearColor					(0, 0, 0, 1);
		ctx.clearDepthf					(1.0f);
		ctx.clear						(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
		ctx.viewport					(m_viewport.left, m_viewport.bottom, m_viewport.width, m_viewport.height);
		ctx.useProgram					(programId);
		ctx.enableVertexAttribArray		(positionLoc);
		ctx.enableVertexAttribArray		(colorLoc);
		ctx.vertexAttribPointer			(positionLoc,	4, GL_FLOAT, GL_FALSE, sizeof(GLfloat[8]), &m_polys[0].p0);
		ctx.vertexAttribPointer			(colorLoc,		4, GL_FLOAT, GL_FALSE, sizeof(GLfloat[8]), &m_polys[0].c0);
		ctx.drawArrays					(GL_TRIANGLES, 0, verticesPerTriangle * (glw::GLsizei)m_polys.size());
		ctx.disableVertexAttribArray	(positionLoc);
		ctx.disableVertexAttribArray	(colorLoc);
		ctx.useProgram					(0);
		ctx.deleteProgram				(programId);
		ctx.finish						();

		ctx.readPixels(dstSurface, 0, 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
	}

	verifyImage(testSurface.getAccess(), refSurface.getAccess());
}

class TriangleCase : public TriangleCaseBase
{
public:
			TriangleCase	(Context& context, const char* name, const char* description, const TriangleData* polysBegin, const TriangleData* polysEnd, const rr::WindowRectangle& viewport);

	void	verifyImage		(const tcu::ConstPixelBufferAccess& testImageAccess, const tcu::ConstPixelBufferAccess& referenceImageAccess);
};

TriangleCase::TriangleCase (Context& context, const char* name, const char* description, const TriangleData* polysBegin, const TriangleData* polysEnd, const rr::WindowRectangle& viewport)
	: TriangleCaseBase(context, name, description, polysBegin, polysEnd, viewport)
{
}

void TriangleCase::verifyImage (const tcu::ConstPixelBufferAccess& testImageAccess, const tcu::ConstPixelBufferAccess& referenceImageAccess)
{
	const int			kernelRadius	= 1;
	const int			faultyLimit		= 6;
	tcu::TestLog&		log				= m_testCtx.getLog();
	tcu::Surface		diffMask		(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
	int					faultyPixels;

	log << TestLog::Message << "Comparing images... " << TestLog::EndMessage;
	log << TestLog::Message << "Deviation within radius of " << kernelRadius << " is allowed." << TestLog::EndMessage;
	log << TestLog::Message << faultyLimit << " faulty pixels are allowed." << TestLog::EndMessage;

	faultyPixels = compareBlackNonBlackImages(log, testImageAccess, referenceImageAccess, diffMask.getAccess(), kernelRadius);

	if (faultyPixels > faultyLimit)
	{
		log << TestLog::ImageSet("Images", "Image comparison")
			<< TestLog::Image("TestImage", "Test image", testImageAccess)
			<< TestLog::Image("ReferenceImage", "Reference image", referenceImageAccess)
			<< TestLog::Image("DifferenceMask", "Difference mask", diffMask.getAccess())
			<< TestLog::EndImageSet
			<< tcu::TestLog::Message << "Got " << faultyPixels << " faulty pixel(s)." << tcu::TestLog::EndMessage;

		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got faulty pixels");
	}
}

class TriangleAttributeCase : public TriangleCaseBase
{
public:
			TriangleAttributeCase	(Context& context, const char* name, const char* description, const TriangleData* polysBegin, const TriangleData* polysEnd, const rr::WindowRectangle& viewport);

	void	verifyImage				(const tcu::ConstPixelBufferAccess& testImageAccess, const tcu::ConstPixelBufferAccess& referenceImageAccess);
};

TriangleAttributeCase::TriangleAttributeCase (Context& context, const char* name, const char* description, const TriangleData* polysBegin, const TriangleData* polysEnd, const rr::WindowRectangle& viewport)
	: TriangleCaseBase(context, name, description, polysBegin, polysEnd, viewport)
{
}

void TriangleAttributeCase::verifyImage (const tcu::ConstPixelBufferAccess& testImageAccess, const tcu::ConstPixelBufferAccess& referenceImageAccess)
{
	const bool		msaa	= m_context.getRenderTarget().getNumSamples() > 1;
	tcu::TestLog&	log		= m_testCtx.getLog();

	if (!msaa)
	{
		const int		kernelRadius	= 1;
		const int		faultyLimit		= 6;
		int				faultyPixels;
		tcu::Surface	diffMask		(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);

		log << TestLog::Message << "Comparing images... " << TestLog::EndMessage;
		log << TestLog::Message << "Deviation within radius of " << kernelRadius << " is allowed." << TestLog::EndMessage;
		log << TestLog::Message << faultyLimit << " faulty pixels are allowed." << TestLog::EndMessage;
		faultyPixels = compareColoredImages(log, testImageAccess, referenceImageAccess, diffMask.getAccess(), kernelRadius);

		if (faultyPixels > faultyLimit)
		{
			log << TestLog::ImageSet("Images", "Image comparison")
				<< TestLog::Image("TestImage", "Test image", testImageAccess)
				<< TestLog::Image("ReferenceImage", "Reference image", referenceImageAccess)
				<< TestLog::Image("DifferenceMask", "Difference mask", diffMask.getAccess())
				<< TestLog::EndImageSet
				<< tcu::TestLog::Message << "Got " << faultyPixels << " faulty pixel(s)." << tcu::TestLog::EndMessage;

			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got faulty pixels");
		}
	}
	else
	{
		const float threshold = 0.3f;
		if (!tcu::fuzzyCompare(log, "Images", "", referenceImageAccess, testImageAccess, threshold, tcu::COMPARE_LOG_ON_ERROR))
			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got faulty pixels");
	}
}

class FillTest : public RenderTestCase
{
public:
								FillTest	(Context& context, const char* name, const char* description, const rr::WindowRectangle& viewport);

	virtual void				render		(sglr::Context& ctx) = DE_NULL;
	void						testRender	(void);

protected:
	const rr::WindowRectangle	m_viewport;
};

FillTest::FillTest (Context& context, const char* name, const char* description, const rr::WindowRectangle& viewport)
	: RenderTestCase(context, name, description)
	, m_viewport	(viewport)
{
}

void FillTest::testRender (void)
{
	using tcu::TestLog;

	const int						numSamples	= 1;

	tcu::TestLog&					log			= m_testCtx.getLog();
	sglr::GLContext					glesContext	(m_context.getRenderContext(), log, 0, tcu::IVec4(0, 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE));
	sglr::ReferenceContextLimits	limits;
	sglr::ReferenceContextBuffers	buffers		(m_context.getRenderTarget().getPixelFormat(), m_context.getRenderTarget().getDepthBits(), 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE, numSamples);
	sglr::ReferenceContext			refContext	(limits, buffers.getColorbuffer(), buffers.getDepthbuffer(), buffers.getStencilbuffer());
	tcu::Surface					testSurface	(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
	tcu::Surface					refSurface	(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);

	render(glesContext);
	glesContext.readPixels(testSurface, 0, 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);

	render(refContext);
	refContext.readPixels(refSurface, 0, 0, TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);

	// check overdraw
	{
		bool				overdrawOk;
		tcu::Surface		outputImage(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);

		log << TestLog::Message << "Checking for overdraw " << TestLog::EndMessage;
		overdrawOk = checkHalfFilledImageOverdraw(log, m_context.getRenderTarget(), testSurface.getAccess(), outputImage.getAccess());

		if (!overdrawOk)
		{
			log << TestLog::ImageSet("Images", "Image comparison")
				<< TestLog::Image("TestImage", "Test image", testSurface.getAccess())
				<< TestLog::Image("InvalidPixels", "Invalid pixels", outputImage.getAccess())
				<< TestLog::EndImageSet
				<< tcu::TestLog::Message << "Got overdraw." << tcu::TestLog::EndMessage;

			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got overdraw");
		}
	}

	// compare & check missing pixels
	{
		const int			kernelRadius	= 1;
		tcu::Surface		diffMask		(TEST_CANVAS_SIZE, TEST_CANVAS_SIZE);
		int					faultyPixels;

		log << TestLog::Message << "Comparing images... " << TestLog::EndMessage;
		log << TestLog::Message << "Deviation within radius of " << kernelRadius << " is allowed." << TestLog::EndMessage;

		blitImageOnBlackSurface(refSurface.getAccess(), refSurface.getAccess()); // makes images look right in Candy

		faultyPixels = compareBlackNonBlackImages(log, testSurface.getAccess(), refSurface.getAccess(), diffMask.getAccess(), kernelRadius);

		if (faultyPixels > 0)
		{
			log << TestLog::ImageSet("Images", "Image comparison")
				<< TestLog::Image("TestImage", "Test image", testSurface.getAccess())
				<< TestLog::Image("ReferenceImage", "Reference image", refSurface.getAccess())
				<< TestLog::Image("DifferenceMask", "Difference mask", diffMask.getAccess())
				<< TestLog::EndImageSet
				<< tcu::TestLog::Message << "Got " << faultyPixels << " faulty pixel(s)." << tcu::TestLog::EndMessage;

			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got faulty pixels");
		}
	}
}

class TriangleFillTest : public FillTest
{
public:
	struct FillTriangle
	{
		tcu::Vec4 v0;
		tcu::Vec4 c0;
		tcu::Vec4 v1;
		tcu::Vec4 c1;
		tcu::Vec4 v2;
		tcu::Vec4 c2;
	};

								TriangleFillTest	(Context& context, const char* name, const char* description, const rr::WindowRectangle& viewport);

	void						render				(sglr::Context& ctx);

protected:
	std::vector<FillTriangle>	m_triangles;
};

TriangleFillTest::TriangleFillTest (Context& context, const char* name, const char* description, const rr::WindowRectangle& viewport)
	: FillTest(context, name, description, viewport)
{
}

void TriangleFillTest::render (sglr::Context& ctx)
{
	const int			verticesPerTriangle		= 3;
	PositionColorShader program;
	const deUint32		programId				= ctx.createProgram(&program);
	const GLint			positionLoc				= ctx.getAttribLocation(programId, "a_position");
	const GLint			colorLoc				= ctx.getAttribLocation(programId, "a_color");
	tcu::TestLog&		log						= m_testCtx.getLog();

	// log the purpose of the test
	log << TestLog::Message << "Viewport: left=" << m_viewport.left << "\tbottom=" << m_viewport.bottom << "\twidth=" << m_viewport.width << "\theight=" << m_viewport.height << TestLog::EndMessage;
	log << TestLog::Message << "Rendering triangles. Coordinates:" << TestLog::EndMessage;
	for (size_t ndx = 0; ndx < m_triangles.size(); ++ndx)
	{
		const std::string v0Properties = genClippingPointInfoString(m_triangles[ndx].v0);
		const std::string v1Properties = genClippingPointInfoString(m_triangles[ndx].v1);
		const std::string v2Properties = genClippingPointInfoString(m_triangles[ndx].v2);

		log << TestLog::Message << "\tv0 (x=" << m_triangles[ndx].v0.x() << "\ty=" << m_triangles[ndx].v0.y() << "\tz=" << m_triangles[ndx].v0.z() << "\tw=" << m_triangles[ndx].v0.w() << ")\t" << v0Properties << TestLog::EndMessage;
		log << TestLog::Message << "\tv1 (x=" << m_triangles[ndx].v1.x() << "\ty=" << m_triangles[ndx].v1.y() << "\tz=" << m_triangles[ndx].v1.z() << "\tw=" << m_triangles[ndx].v1.w() << ")\t" << v1Properties << TestLog::EndMessage;
		log << TestLog::Message << "\tv2 (x=" << m_triangles[ndx].v2.x() << "\ty=" << m_triangles[ndx].v2.y() << "\tz=" << m_triangles[ndx].v2.z() << "\tw=" << m_triangles[ndx].v2.w() << ")\t" << v2Properties << TestLog::EndMessage;
		log << TestLog::Message << TestLog::EndMessage;
	}

	ctx.clearColor					(0, 0, 0, 1);
	ctx.clearDepthf					(1.0f);
	ctx.clear						(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	ctx.viewport					(m_viewport.left, m_viewport.bottom, m_viewport.width, m_viewport.height);
	ctx.useProgram					(programId);
	ctx.blendFunc					(GL_ONE, GL_ONE);
	ctx.enable						(GL_BLEND);
	ctx.enableVertexAttribArray		(positionLoc);
	ctx.enableVertexAttribArray		(colorLoc);
	ctx.vertexAttribPointer			(positionLoc,	4, GL_FLOAT, GL_FALSE, sizeof(GLfloat[8]), &m_triangles[0].v0);
	ctx.vertexAttribPointer			(colorLoc,		4, GL_FLOAT, GL_FALSE, sizeof(GLfloat[8]), &m_triangles[0].c0);
	ctx.drawArrays					(GL_TRIANGLES, 0, verticesPerTriangle * (glw::GLsizei)m_triangles.size());
	ctx.disableVertexAttribArray	(positionLoc);
	ctx.disableVertexAttribArray	(colorLoc);
	ctx.useProgram					(0);
	ctx.deleteProgram				(programId);
	ctx.finish						();
}

class QuadFillTest : public TriangleFillTest
{
public:
	QuadFillTest (Context& context, const char* name, const char* description, const rr::WindowRectangle& viewport, const tcu::Vec3& d1, const tcu::Vec3& d2, const tcu::Vec3& center_ = tcu::Vec3(0, 0, 0));
};

QuadFillTest::QuadFillTest (Context& context, const char* name, const char* description, const rr::WindowRectangle& viewport, const tcu::Vec3& d1, const tcu::Vec3& d2, const tcu::Vec3& center_)
	: TriangleFillTest(context, name, description, viewport)
{
	const float		radius		= 40000.0f;
	const tcu::Vec4 center		= tcu::Vec4(center_.x(), center_.y(), center_.z(), 1.0f);
	const tcu::Vec4 halfWhite	= tcu::Vec4(0.5f, 0.5f, 0.5f, 0.5f);
	const tcu::Vec4 halfRed		= tcu::Vec4(0.5f, 0.0f, 0.0f, 0.5f);
	const tcu::Vec4	e1			= radius * tcu::Vec4(d1.x(), d1.y(), d1.z(), 0.0f);
	const tcu::Vec4	e2			= radius * tcu::Vec4(d2.x(), d2.y(), d2.z(), 0.0f);

	FillTriangle triangle1;
	FillTriangle triangle2;

	triangle1.c0 = halfWhite;
	triangle1.c1 = halfWhite;
	triangle1.c2 = halfWhite;
	triangle1.v0 = center + e1 + e2;
	triangle1.v1 = center + e1 - e2;
	triangle1.v2 = center - e1 - e2;
	m_triangles.push_back(triangle1);

	triangle2.c0 = halfRed;
	triangle2.c1 = halfRed;
	triangle2.c2 = halfRed;
	triangle2.v0 = center + e1 + e2;
	triangle2.v1 = center - e1 - e2;
	triangle2.v2 = center - e1 + e2;
	m_triangles.push_back(triangle2);
}

class TriangleFanFillTest : public TriangleFillTest
{
public:
	TriangleFanFillTest (Context& context, const char* name, const char* description, const rr::WindowRectangle& viewport);
};

TriangleFanFillTest::TriangleFanFillTest (Context& context, const char* name, const char* description, const rr::WindowRectangle& viewport)
	: TriangleFillTest(context, name, description, viewport)
{
	const float		radius				= 70000.0f;
	const int		trianglesPerVisit	= 40;
	const tcu::Vec4 center				= tcu::Vec4(0, 0, 0, 1.0f);
	const tcu::Vec4 halfWhite			= tcu::Vec4(0.5f, 0.5f, 0.5f, 0.5f);
	const tcu::Vec4 oddSliceColor		= tcu::Vec4(0.0f, 0.0f, 0.5f, 0.0f);

	// create a continuous surface that goes through all 6 clip planes

	/*
		*   /           /
		*  /_ _ _ _ _  /x
		* |           |  |
		* |           | /
		* |       / --xe /
		* |      |    | /
		* |_ _ _ e _ _|/
		*
		* e = enter
		* x = exit
		*/
	const struct ClipPlaneVisit
	{
		const tcu::Vec3 corner;
		const tcu::Vec3 entryPoint;
		const tcu::Vec3 exitPoint;
	} visits[] =
	{
		{ tcu::Vec3( 1, 1, 1),	tcu::Vec3( 0, 1, 1),	tcu::Vec3( 1, 0, 1) },
		{ tcu::Vec3( 1,-1, 1),	tcu::Vec3( 1, 0, 1),	tcu::Vec3( 1,-1, 0) },
		{ tcu::Vec3( 1,-1,-1),	tcu::Vec3( 1,-1, 0),	tcu::Vec3( 0,-1,-1) },
		{ tcu::Vec3(-1,-1,-1),	tcu::Vec3( 0,-1,-1),	tcu::Vec3(-1, 0,-1) },
		{ tcu::Vec3(-1, 1,-1),	tcu::Vec3(-1, 0,-1),	tcu::Vec3(-1, 1, 0) },
		{ tcu::Vec3(-1, 1, 1),	tcu::Vec3(-1, 1, 0),	tcu::Vec3( 0, 1, 1) },
	};

	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(visits); ++ndx)
	{
		const ClipPlaneVisit& visit = visits[ndx];

		for (int tri = 0; tri < trianglesPerVisit; ++tri)
		{
			tcu::Vec3 vertex0;
			tcu::Vec3 vertex1;

			if (tri == 0) // first vertex is magic
			{
				vertex0 = visit.entryPoint;
			}
			else
			{
				const tcu::Vec3 v1 = visit.entryPoint - visit.corner;
				const tcu::Vec3 v2 = visit.exitPoint  - visit.corner;

				vertex0 = visit.corner + tcu::normalize(tcu::mix(v1, v2, tcu::Vec3(float(tri)/trianglesPerVisit)));
			}

			if (tri == trianglesPerVisit-1) // last vertex is magic
			{
				vertex1 = visit.exitPoint;
			}
			else
			{
				const tcu::Vec3 v1 = visit.entryPoint - visit.corner;
				const tcu::Vec3 v2 = visit.exitPoint  - visit.corner;

				vertex1 = visit.corner + tcu::normalize(tcu::mix(v1, v2, tcu::Vec3(float(tri+1)/trianglesPerVisit)));
			}

			// write vec out
			{
				FillTriangle triangle;

				triangle.c0 = (tri % 2) ? halfWhite : halfWhite + oddSliceColor;
				triangle.c1 = (tri % 2) ? halfWhite : halfWhite + oddSliceColor;
				triangle.c2 = (tri % 2) ? halfWhite : halfWhite + oddSliceColor;
				triangle.v0 = center;
				triangle.v1 = tcu::Vec4(vertex0.x() * radius, vertex0.y() * radius, vertex0.z() * radius, 1.0f);
				triangle.v2 = tcu::Vec4(vertex1.x() * radius, vertex1.y() * radius, vertex1.z() * radius, 1.0f);

				m_triangles.push_back(triangle);
			}

		}
	}
}

class PointsTestGroup : public TestCaseGroup
{
public:
			PointsTestGroup	(Context& context);

	void	init			(void);
};

PointsTestGroup::PointsTestGroup (Context& context)
	: TestCaseGroup(context, "point", "Point clipping tests")
{
}

void PointsTestGroup::init (void)
{
	const float littleOverViewport = 1.0f + (2.0f / (TEST_CANVAS_SIZE)); // one pixel over the viewport edge in VIEWPORT_WHOLE, half pixels over in the reduced viewport.

	const tcu::Vec4 viewportTestPoints[] =
	{
		// in clip volume
		tcu::Vec4( 0.0f,  0.0f,  0.0f,  1.0f),
		tcu::Vec4( 0.1f,  0.1f,  0.1f,  1.0f),
		tcu::Vec4(-0.1f,  0.1f, -0.1f,  1.0f),
		tcu::Vec4(-0.1f, -0.1f,  0.1f,  1.0f),
		tcu::Vec4( 0.1f, -0.1f, -0.1f,  1.0f),

		// in clip volume with w != 1
		tcu::Vec4( 2.0f,  2.0f,  2.0f,  3.0f),
		tcu::Vec4(-2.0f, -2.0f,  2.0f,  3.0f),
		tcu::Vec4( 0.5f, -0.5f,  0.5f,  0.7f),
		tcu::Vec4(-0.5f,  0.5f, -0.5f,  0.7f),

		// near the edge
		tcu::Vec4(-2.0f, -2.0f,  0.0f,  2.2f),
		tcu::Vec4( 1.0f, -1.0f,  0.0f,  1.1f),
		tcu::Vec4(-1.0f,  1.0f,  0.0f,  1.1f),

		// not in the volume but still between near and far planes
		tcu::Vec4( 1.3f,  0.0f,  0.0f,  1.0f),
		tcu::Vec4(-1.3f,  0.0f,  0.0f,  1.0f),
		tcu::Vec4( 0.0f,  1.3f,  0.0f,  1.0f),
		tcu::Vec4( 0.0f, -1.3f,  0.0f,  1.0f),

		tcu::Vec4(-1.3f, -1.3f,  0.0f,  1.0f),
		tcu::Vec4(-1.3f,  1.3f,  0.0f,  1.0f),
		tcu::Vec4( 1.3f,  1.3f,  0.0f,  1.0f),
		tcu::Vec4( 1.3f, -1.3f,  0.0f,  1.0f),

		// outside the viewport, wide points have fragments in the viewport
		tcu::Vec4( littleOverViewport,  littleOverViewport,  0.0f,  1.0f),
		tcu::Vec4(               0.0f,  littleOverViewport,  0.0f,  1.0f),
		tcu::Vec4( littleOverViewport,                0.0f,  0.0f,  1.0f),
	};
	const tcu::Vec4 depthTestPoints[] =
	{
		// in clip volume
		tcu::Vec4( 0.0f,  0.0f,  0.0f,  1.0f),
		tcu::Vec4( 0.1f,  0.1f,  0.1f,  1.0f),
		tcu::Vec4(-0.1f,  0.1f, -0.1f,  1.0f),
		tcu::Vec4(-0.1f, -0.1f,  0.1f,  1.0f),
		tcu::Vec4( 0.1f, -0.1f, -0.1f,  1.0f),

		// not between the near and the far planes. These should be clipped
		tcu::Vec4( 0.1f,  0.0f,  1.1f,  1.0f),
		tcu::Vec4(-0.1f,  0.0f, -1.1f,  1.0f),
		tcu::Vec4(-0.0f, -0.1f,  1.1f,  1.0f),
		tcu::Vec4( 0.0f,  0.1f, -1.1f,  1.0f)
	};

	addChild(new PointCase(m_context, "point_z_clip",						"point z clipping",				DE_ARRAY_BEGIN(depthTestPoints),	DE_ARRAY_END(depthTestPoints),		1.0f,	VIEWPORT_WHOLE));
	addChild(new PointCase(m_context, "point_z_clip_viewport_center",		"point z clipping",				DE_ARRAY_BEGIN(depthTestPoints),	DE_ARRAY_END(depthTestPoints),		1.0f,	VIEWPORT_CENTER));
	addChild(new PointCase(m_context, "point_z_clip_viewport_corner",		"point z clipping",				DE_ARRAY_BEGIN(depthTestPoints),	DE_ARRAY_END(depthTestPoints),		1.0f,	VIEWPORT_CORNER));

	addChild(new PointCase(m_context, "point_clip_viewport_center",			"point viewport clipping",		DE_ARRAY_BEGIN(viewportTestPoints), DE_ARRAY_END(viewportTestPoints),	1.0f,	VIEWPORT_CENTER));
	addChild(new PointCase(m_context, "point_clip_viewport_corner",			"point viewport clipping",		DE_ARRAY_BEGIN(viewportTestPoints), DE_ARRAY_END(viewportTestPoints),	1.0f,	VIEWPORT_CORNER));

	addChild(new PointCase(m_context, "wide_point_z_clip",					"point z clipping",				DE_ARRAY_BEGIN(depthTestPoints),	DE_ARRAY_END(depthTestPoints),		5.0f,	VIEWPORT_WHOLE));
	addChild(new PointCase(m_context, "wide_point_z_clip_viewport_center",	"point z clipping",				DE_ARRAY_BEGIN(depthTestPoints),	DE_ARRAY_END(depthTestPoints),		5.0f,	VIEWPORT_CENTER));
	addChild(new PointCase(m_context, "wide_point_z_clip_viewport_corner",	"point z clipping",				DE_ARRAY_BEGIN(depthTestPoints),	DE_ARRAY_END(depthTestPoints),		5.0f,	VIEWPORT_CORNER));

	addChild(new PointCase(m_context, "wide_point_clip",					"point viewport clipping",		DE_ARRAY_BEGIN(viewportTestPoints), DE_ARRAY_END(viewportTestPoints),	5.0f,	VIEWPORT_WHOLE));
	addChild(new PointCase(m_context, "wide_point_clip_viewport_center",	"point viewport clipping",		DE_ARRAY_BEGIN(viewportTestPoints), DE_ARRAY_END(viewportTestPoints),	5.0f,	VIEWPORT_CENTER));
	addChild(new PointCase(m_context, "wide_point_clip_viewport_corner",	"point viewport clipping",		DE_ARRAY_BEGIN(viewportTestPoints), DE_ARRAY_END(viewportTestPoints),	5.0f,	VIEWPORT_CORNER));
}

class LinesTestGroup : public TestCaseGroup
{
public:
			LinesTestGroup	(Context& context);

	void	init			(void);
};

LinesTestGroup::LinesTestGroup (Context& context)
	: TestCaseGroup(context, "line", "Line clipping tests")
{
}

void LinesTestGroup::init (void)
{
	const float littleOverViewport = 1.0f + (2.0f / (TEST_CANVAS_SIZE)); // one pixel over the viewport edge in VIEWPORT_WHOLE, half pixels over in the reduced viewport.

	// lines
	const LineRenderTestCase::ColorlessLineData viewportTestLines[] =
	{
		// from center to outside of viewport
		{tcu::Vec4( 0.0f,  0.0f,  0.0f,  1.0f),		tcu::Vec4( 0.0f,  1.5f,  0.0f,  1.0f)},
		{tcu::Vec4( 0.0f,  0.0f,  0.0f,  1.0f),		tcu::Vec4(-1.5f,  1.0f,  0.0f,  1.0f)},
		{tcu::Vec4( 0.0f,  0.0f,  0.0f,  1.0f),		tcu::Vec4(-1.5f,  0.0f,  0.0f,  1.0f)},
		{tcu::Vec4( 0.0f,  0.0f,  0.0f,  1.0f),		tcu::Vec4( 0.2f,  0.4f,  1.5f,  1.0f)},
		{tcu::Vec4( 0.0f,  0.0f,  0.0f,  1.0f),		tcu::Vec4(-2.0f, -1.0f,  0.0f,  1.0f)},
		{tcu::Vec4( 0.0f,  0.0f,  0.0f,  1.0f),		tcu::Vec4( 1.0f,  0.1f,  0.0f,  0.6f)},

		// from outside to inside of viewport
		{tcu::Vec4( 1.5f,  0.0f,  0.0f,  1.0f),		tcu::Vec4( 0.8f, -0.2f,  0.0f,  1.0f)},
		{tcu::Vec4( 0.0f, -1.5f,  0.0f,  1.0f),		tcu::Vec4( 0.9f, -0.7f,  0.0f,  1.0f)},

		// from outside to outside
		{tcu::Vec4( 0.0f, -1.3f,  0.0f,  1.0f),		tcu::Vec4( 1.3f,  0.0f,  0.0f,  1.0f)},

		// outside the viewport, wide lines have fragments in the viewport
		{tcu::Vec4(-0.8f,                      -littleOverViewport,  0.0f,  1.0f),	tcu::Vec4( 0.0f, -littleOverViewport,         0.0f,  1.0f)},
		{tcu::Vec4(-littleOverViewport - 1.0f,  0.0f,                0.0f,  1.0f),	tcu::Vec4( 0.0f, -littleOverViewport - 1.0f,  0.0f,  1.0f)},
	};
	const LineRenderTestCase::ColorlessLineData depthTestLines[] =
	{
		{tcu::Vec4( 0.0f,  0.0f,  0.0f,  1.0f),		tcu::Vec4( 1.3f,  1.0f,  2.0f,  1.0f)},
		{tcu::Vec4( 0.0f,  0.0f,  0.0f,  1.0f),		tcu::Vec4( 1.3f, -1.0f,  2.0f,  1.0f)},
		{tcu::Vec4( 0.0f,  0.0f,  0.0f,  1.0f),		tcu::Vec4(-1.0f, -1.1f, -2.0f,  1.0f)},
		{tcu::Vec4( 0.0f,  0.0f,  0.0f,  1.0f),		tcu::Vec4(-1.0f,  1.1f, -2.0f,  1.0f)},
		{tcu::Vec4( 0.0f,  0.0f,  0.0f,  1.0f),		tcu::Vec4( 1.0f,  0.1f,  2.0f,  0.6f)},
	};
	const LineRenderTestCase::ColorlessLineData longTestLines[] =
	{
		{tcu::Vec4( -41000.0f,		-40000.0f,		-1000000.0f,	1.0f),	tcu::Vec4( 41000.0f,		40000.0f,		1000000.0f,	1.0f)},
		{tcu::Vec4(  41000.0f,		-40000.0f,		 1000000.0f,	1.0f),	tcu::Vec4(-41000.0f,		40000.0f,	   -1000000.0f,	1.0f)},
		{tcu::Vec4(  0.5f,			-40000.0f,		 100000.0f,		1.0f),	tcu::Vec4( 0.5f,			40000.0f,	   -100000.0f,	1.0f)},
		{tcu::Vec4( -0.5f,			 40000.0f,		 100000.0f,		1.0f),	tcu::Vec4(-0.5f,		   -40000.0f,	   -100000.0f,	1.0f)},
	};

	// line attribute clipping
	const tcu::Vec4 red			(1.0f, 0.0f, 0.0f, 1.0f);
	const tcu::Vec4 yellow		(1.0f, 1.0f, 0.0f, 1.0f);
	const tcu::Vec4 lightBlue	(0.3f, 0.3f, 1.0f, 1.0f);
	const LineRenderTestCase::ColoredLineData colorTestLines[] =
	{
		{tcu::Vec4( 0.0f,  0.0f,  0.0f,  1.0f),	red,	tcu::Vec4( 1.3f,  1.0f,  2.0f,  1.0f),	yellow		},
		{tcu::Vec4( 0.0f,  0.0f,  0.0f,  1.0f),	red,	tcu::Vec4( 1.3f, -1.0f,  2.0f,  1.0f),	lightBlue	},
		{tcu::Vec4( 0.0f,  0.0f,  0.0f,  1.0f),	red,	tcu::Vec4(-1.0f, -1.0f, -2.0f,  1.0f),	yellow		},
		{tcu::Vec4( 0.0f,  0.0f,  0.0f,  1.0f),	red,	tcu::Vec4(-1.0f,  1.0f, -2.0f,  1.0f),	lightBlue	},
	};

	// line clipping
	addChild(new LineCase(m_context, "line_z_clip",							"line z clipping",				DE_ARRAY_BEGIN(depthTestLines),		DE_ARRAY_END(depthTestLines),		1.0f,	VIEWPORT_WHOLE));
	addChild(new LineCase(m_context, "line_z_clip_viewport_center",			"line z clipping",				DE_ARRAY_BEGIN(depthTestLines),		DE_ARRAY_END(depthTestLines),		1.0f,	VIEWPORT_CENTER));
	addChild(new LineCase(m_context, "line_z_clip_viewport_corner",			"line z clipping",				DE_ARRAY_BEGIN(depthTestLines),		DE_ARRAY_END(depthTestLines),		1.0f,	VIEWPORT_CORNER));

	addChild(new LineCase(m_context, "line_clip_viewport_center",			"line viewport clipping",		DE_ARRAY_BEGIN(viewportTestLines),	DE_ARRAY_END(viewportTestLines),	1.0f,	VIEWPORT_CENTER));
	addChild(new LineCase(m_context, "line_clip_viewport_corner",			"line viewport clipping",		DE_ARRAY_BEGIN(viewportTestLines),	DE_ARRAY_END(viewportTestLines),	1.0f,	VIEWPORT_CORNER));

	addChild(new LineCase(m_context, "wide_line_z_clip",					"line z clipping",				DE_ARRAY_BEGIN(depthTestLines),		DE_ARRAY_END(depthTestLines),		5.0f,	VIEWPORT_WHOLE));
	addChild(new LineCase(m_context, "wide_line_z_clip_viewport_center",	"line z clipping",				DE_ARRAY_BEGIN(depthTestLines),		DE_ARRAY_END(depthTestLines),		5.0f,	VIEWPORT_CENTER));
	addChild(new LineCase(m_context, "wide_line_z_clip_viewport_corner",	"line z clipping",				DE_ARRAY_BEGIN(depthTestLines),		DE_ARRAY_END(depthTestLines),		5.0f,	VIEWPORT_CORNER));

	addChild(new LineCase(m_context, "wide_line_clip",						"line viewport clipping",		DE_ARRAY_BEGIN(viewportTestLines),	DE_ARRAY_END(viewportTestLines),	5.0f,	VIEWPORT_WHOLE));
	addChild(new LineCase(m_context, "wide_line_clip_viewport_center",		"line viewport clipping",		DE_ARRAY_BEGIN(viewportTestLines),	DE_ARRAY_END(viewportTestLines),	5.0f,	VIEWPORT_CENTER));
	addChild(new LineCase(m_context, "wide_line_clip_viewport_corner",		"line viewport clipping",		DE_ARRAY_BEGIN(viewportTestLines),	DE_ARRAY_END(viewportTestLines),	5.0f,	VIEWPORT_CORNER));

	addChild(new LineCase(m_context, "long_line_clip",						"line viewport clipping",		DE_ARRAY_BEGIN(longTestLines),		DE_ARRAY_END(longTestLines),		1.0f,	VIEWPORT_WHOLE, 2));
	addChild(new LineCase(m_context, "long_wide_line_clip",					"line viewport clipping",		DE_ARRAY_BEGIN(longTestLines),		DE_ARRAY_END(longTestLines),		5.0f,	VIEWPORT_WHOLE, 2));

	// line attribute clipping
	addChild(new ColoredLineCase(m_context, "line_attrib_clip",				"line attribute clipping",		DE_ARRAY_BEGIN(colorTestLines),		DE_ARRAY_END(colorTestLines),		1.0f,	VIEWPORT_WHOLE));
	addChild(new ColoredLineCase(m_context, "wide_line_attrib_clip",		"line attribute clipping",		DE_ARRAY_BEGIN(colorTestLines),		DE_ARRAY_END(colorTestLines),		5.0f,	VIEWPORT_WHOLE));
}

class PolysTestGroup : public TestCaseGroup
{
public:
			PolysTestGroup	(Context& context);

	void	init			(void);
};

PolysTestGroup::PolysTestGroup (Context& context)
	: TestCaseGroup(context, "polygon", "Polygon clipping tests")
{
}

void PolysTestGroup::init (void)
{
	const float		large = 100000.0f;
	const float		offset = 0.9f;
	const tcu::Vec4 white	(1.0f, 1.0f, 1.0f, 1.0f);
	const tcu::Vec4 red		(1.0f, 0.0f, 0.0f, 1.0f);
	const tcu::Vec4 yellow	(1.0f, 1.0f, 0.0f, 1.0f);
	const tcu::Vec4 blue	(0.0f, 0.0f, 1.0f, 1.0f);

	// basic cases
	{
		const TriangleCase::TriangleData viewportPolys[] =
		{
			// one vertex clipped
			{tcu::Vec4(-0.8f, -0.2f,  0.0f,  1.0f), white, tcu::Vec4(-0.8f,  0.2f,  0.0f,  1.0f), white, tcu::Vec4(-1.3f,  0.05f,  0.0f,  1.0f), white},

			// two vertices clipped
			{tcu::Vec4(-0.6f, -1.2f,  0.0f,  1.0f), white, tcu::Vec4(-1.2f, -0.6f,  0.0f,  1.0f), white, tcu::Vec4(-0.6f, -0.6f,  0.0f,  1.0f), white},

			// three vertices clipped
			{tcu::Vec4(-1.1f,  0.6f,  0.0f,  1.0f), white, tcu::Vec4(-1.1f,  1.1f,  0.0f,  1.0f), white, tcu::Vec4(-0.6f,  1.1f,  0.0f,  1.0f), white},
			{tcu::Vec4( 0.8f,  1.1f,  0.0f,  1.0f), white, tcu::Vec4( 0.95f,-1.1f,  0.0f,  1.0f), white, tcu::Vec4( 3.0f,  0.0f,  0.0f,  1.0f), white},
		};
		const TriangleCase::TriangleData depthPolys[] =
		{
			// one vertex clipped to Z+
			{tcu::Vec4(-0.2f,  0.7f,  0.0f,  1.0f), white, tcu::Vec4( 0.2f,  0.7f,  0.0f,  1.0f), white, tcu::Vec4( 0.0f,  0.9f,  2.0f,  1.0f), white},

			// two vertices clipped to Z-
			{tcu::Vec4( 0.9f, 0.4f,  -1.5f,  1.0f), white, tcu::Vec4( 0.9f, -0.4f, -1.5f,  1.0f), white, tcu::Vec4( 0.6f,  0.0f,  0.0f,  1.0f), white},

			// three vertices clipped
			{tcu::Vec4(-0.9f, 0.6f,  -2.0f,  1.0f), white, tcu::Vec4(-0.9f, -0.6f, -2.0f,  1.0f), white, tcu::Vec4(-0.4f,  0.0f,  2.0f,  1.0f), white},

			// three vertices clipped by X, Y and Z
			{tcu::Vec4( 0.0f, -1.2f,  0.0f,  1.0f), white, tcu::Vec4( 0.0f,  0.5f,  -1.5f, 1.0f), white, tcu::Vec4( 1.2f, -0.9f,  0.0f,  1.0f), white},
		};
		const TriangleCase::TriangleData largePolys[] =
		{
			// one vertex clipped
			{tcu::Vec4(-0.2f,  -0.3f,  0.0f,  1.0f), white, tcu::Vec4( 0.2f, -0.3f,  0.0f,  1.0f), white, tcu::Vec4( 0.0f, -large,  2.0f,  1.0f), white},

			// two vertices clipped
			{tcu::Vec4( 0.5f, 0.5f,  0.0f,  1.0f), white, tcu::Vec4( large, 0.5f, 0.0f,  1.0f), white, tcu::Vec4( 0.5f,  large,  0.0f,  1.0f), white},

			// three vertices clipped
			{tcu::Vec4(-0.9f, -large, 0.0f,  1.0f), white, tcu::Vec4(-1.1f, -large, 0.0f,  1.0f), white, tcu::Vec4(-0.9f,  large,  0.0f,  1.0f), white},
		};
		const TriangleCase::TriangleData largeDepthPolys[] =
		{
			// one vertex clipped
			{tcu::Vec4(-0.2f,  -0.3f,  0.0f,  1.0f), white, tcu::Vec4( 0.2f, -0.3f,  0.0f,  1.0f), white, tcu::Vec4( 0.0f, -large, large,  1.0f), white},

			// two vertices clipped
			{tcu::Vec4( 0.5f, 0.5f,  0.0f,  1.0f), white, tcu::Vec4( 0.9f, large/2, -large,  1.0f), white, tcu::Vec4( large/4, 0.0f, -large,  1.0f), white},

			// three vertices clipped
			{tcu::Vec4(-0.9f, large/4, large,  1.0f), white, tcu::Vec4(-0.5f, -large/4, -large,  1.0f), white, tcu::Vec4(-0.2f, large/4, large,  1.0f), white},
		};
		const TriangleCase::TriangleData attribPolys[] =
		{
			// one vertex clipped to edge, large
			{tcu::Vec4(-0.2f,  -0.3f,  0.0f,  1.0f), red, tcu::Vec4( 0.2f, -0.3f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.0f, -large,  2.0f,  1.0f), blue},

			// two vertices clipped to edges
			{tcu::Vec4( 0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f,  0.6f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.6f,  0.6f,  0.0f,  1.0f), blue},

			// two vertices clipped to edges, with non-uniform w
			{tcu::Vec4( 0.6f, -1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f, -0.6f,  0.0f,  1.0f), yellow, 16.0f*tcu::Vec4( 0.6f, -0.6f,  0.0f,  1.0f), blue},

			// three vertices clipped, large, Z
			{tcu::Vec4(-0.9f, large/4, large,  1.0f), red, tcu::Vec4(-0.5f, -large/4, -large,  1.0f), yellow, tcu::Vec4(-0.2f, large/4, large,  1.0f), blue},
		};

		addChild(new TriangleCase(m_context, "poly_clip_viewport_center",			"polygon viewport clipping",	DE_ARRAY_BEGIN(viewportPolys),		DE_ARRAY_END(viewportPolys),	VIEWPORT_CENTER));
		addChild(new TriangleCase(m_context, "poly_clip_viewport_corner",			"polygon viewport clipping",	DE_ARRAY_BEGIN(viewportPolys),		DE_ARRAY_END(viewportPolys),	VIEWPORT_CORNER));

		addChild(new TriangleCase(m_context, "poly_z_clip",							"polygon z clipping",			DE_ARRAY_BEGIN(depthPolys),			DE_ARRAY_END(depthPolys),		VIEWPORT_WHOLE));
		addChild(new TriangleCase(m_context, "poly_z_clip_viewport_center",			"polygon z clipping",			DE_ARRAY_BEGIN(depthPolys),			DE_ARRAY_END(depthPolys),		VIEWPORT_CENTER));
		addChild(new TriangleCase(m_context, "poly_z_clip_viewport_corner",			"polygon z clipping",			DE_ARRAY_BEGIN(depthPolys),			DE_ARRAY_END(depthPolys),		VIEWPORT_CORNER));

		addChild(new TriangleCase(m_context, "large_poly_clip_viewport_center",		"polygon viewport clipping",	DE_ARRAY_BEGIN(largePolys),			DE_ARRAY_END(largePolys),		VIEWPORT_CENTER));
		addChild(new TriangleCase(m_context, "large_poly_clip_viewport_corner",		"polygon viewport clipping",	DE_ARRAY_BEGIN(largePolys),			DE_ARRAY_END(largePolys),		VIEWPORT_CORNER));

		addChild(new TriangleCase(m_context, "large_poly_z_clip",					"polygon z clipping",			DE_ARRAY_BEGIN(largeDepthPolys),	DE_ARRAY_END(largeDepthPolys),	VIEWPORT_WHOLE));
		addChild(new TriangleCase(m_context, "large_poly_z_clip_viewport_center",	"polygon z clipping",			DE_ARRAY_BEGIN(largeDepthPolys),	DE_ARRAY_END(largeDepthPolys),	VIEWPORT_CENTER));
		addChild(new TriangleCase(m_context, "large_poly_z_clip_viewport_corner",	"polygon z clipping",			DE_ARRAY_BEGIN(largeDepthPolys),	DE_ARRAY_END(largeDepthPolys),	VIEWPORT_CORNER));

		addChild(new TriangleAttributeCase(m_context, "poly_attrib_clip",					"polygon clipping",		DE_ARRAY_BEGIN(attribPolys),		DE_ARRAY_END(attribPolys),		VIEWPORT_WHOLE));
		addChild(new TriangleAttributeCase(m_context, "poly_attrib_clip_viewport_center",	"polygon clipping",		DE_ARRAY_BEGIN(attribPolys),		DE_ARRAY_END(attribPolys),		VIEWPORT_CENTER));
		addChild(new TriangleAttributeCase(m_context, "poly_attrib_clip_viewport_corner",	"polygon clipping",		DE_ARRAY_BEGIN(attribPolys),		DE_ARRAY_END(attribPolys),		VIEWPORT_CORNER));
	}

	// multiple polygons
	{
		{
			const TriangleAttributeCase::TriangleData polys[] =
			{
				// one vertex clipped to edge
				{tcu::Vec4(-0.2f,  -0.3f,  0.0f,  1.0f), red, tcu::Vec4( 0.2f, -0.3f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.0f, -offset,  2.0f,  1.0f), blue},

				// two vertices clipped to edges
				{tcu::Vec4( 0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f,  0.6f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.6f,  0.6f,  0.0f,  1.0f), blue},

				// two vertices clipped to edges, with non-uniform w
				{tcu::Vec4( 0.6f, -1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f, -0.6f,  0.0f,  1.0f), yellow, 16.0f*tcu::Vec4( 0.6f, -0.6f,  0.0f,  1.0f), blue},
				{tcu::Vec4( 0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f,  0.6f,  0.0f,  1.0f), yellow, 16.0f*tcu::Vec4( 0.6f,  0.6f,  0.0f,  1.0f), blue},
				{tcu::Vec4(-0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4(-1.2f,  0.6f,  0.0f,  1.0f), yellow, 16.0f*tcu::Vec4(-0.6f,  0.6f,  0.0f,  1.0f), blue},
				{tcu::Vec4(-0.6f, -1.2f,  0.0f,  1.0f), red, tcu::Vec4(-1.2f, -0.6f,  0.0f,  1.0f), yellow, 16.0f*tcu::Vec4(-0.6f, -0.6f,  0.0f,  1.0f), blue},

				// three vertices clipped, Z
				{tcu::Vec4(-0.9f, offset/4, offset,  1.0f), red, tcu::Vec4(-0.5f, -offset/4, -offset,  1.0f), yellow, tcu::Vec4(-0.2f, offset/4, offset,  1.0f), blue},
			};

			addChild(new TriangleAttributeCase(m_context, "multiple_0",					"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_WHOLE));
			addChild(new TriangleAttributeCase(m_context, "multiple_0_viewport_center",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CENTER));
			addChild(new TriangleAttributeCase(m_context, "multiple_0_viewport_corner",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CORNER));
		}

		{
			const TriangleAttributeCase::TriangleData polys[] =
			{
				// one vertex clipped to z
				{tcu::Vec4(-0.2f,  -0.3f,  0.0f,  1.0f), red, tcu::Vec4( 0.2f, -0.3f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.0f, -offset,  2.0f,  1.0f), blue},

				// two vertices clipped to edges
				{tcu::Vec4( 0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f,  0.6f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.6f,  0.6f,  0.0f,  1.0f), blue},

				// two vertices clipped to edges, with non-uniform w
				{tcu::Vec4( 0.6f, -1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f, -0.6f,  0.0f,  1.0f), yellow, 16.0f*tcu::Vec4( 0.6f, -0.6f,  0.0f,  1.0f), blue},
				{tcu::Vec4( 0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f,  0.6f,  0.0f,  1.0f), yellow, 16.0f*tcu::Vec4( 0.6f,  0.6f,  0.0f,  1.0f), blue},
				{tcu::Vec4(-0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4(-1.2f,  0.6f,  0.0f,  1.0f), yellow, 16.0f*tcu::Vec4(-0.6f,  0.6f,  0.0f,  1.0f), blue},
				{tcu::Vec4(-0.6f, -1.2f,  0.0f,  1.0f), red, tcu::Vec4(-1.2f, -0.6f,  0.0f,  1.0f), yellow, 16.0f*tcu::Vec4(-0.6f, -0.6f,  0.0f,  1.0f), blue},
			};

			addChild(new TriangleAttributeCase(m_context, "multiple_1",					"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_WHOLE));
			addChild(new TriangleAttributeCase(m_context, "multiple_1_viewport_center",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CENTER));
			addChild(new TriangleAttributeCase(m_context, "multiple_1_viewport_corner",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CORNER));
		}

		{
			const TriangleAttributeCase::TriangleData polys[] =
			{
				// one vertex clipped to z
				{tcu::Vec4(-0.2f,  -0.3f,  0.0f,  1.0f), red, tcu::Vec4( 0.2f, -0.3f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.0f, -offset,  2.0f,  1.0f), blue},

				// two vertices clipped to edges
				{tcu::Vec4( 0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f,  0.6f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.6f,  0.6f,  0.0f,  1.0f), blue},

				// two vertices clipped to edges
				{tcu::Vec4( 0.6f, -1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f, -0.6f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.6f, -0.6f,  0.0f,  1.0f), blue},
				{tcu::Vec4( 0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f,  0.6f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.6f,  0.6f,  0.0f,  1.0f), blue},
				{tcu::Vec4(-0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4(-1.2f,  0.6f,  0.0f,  1.0f), yellow, tcu::Vec4(-0.6f,  0.6f,  0.0f,  1.0f), blue},
				{tcu::Vec4(-0.6f, -1.2f,  0.0f,  1.0f), red, tcu::Vec4(-1.2f, -0.6f,  0.0f,  1.0f), yellow, tcu::Vec4(-0.6f, -0.6f,  0.0f,  1.0f), blue},
			};

			addChild(new TriangleAttributeCase(m_context, "multiple_2",					"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_WHOLE));
			addChild(new TriangleAttributeCase(m_context, "multiple_2_viewport_center",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CENTER));
			addChild(new TriangleAttributeCase(m_context, "multiple_2_viewport_corner",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CORNER));
		}

		{
			const TriangleAttributeCase::TriangleData polys[] =
			{
				// one vertex clipped to z
				{tcu::Vec4(-0.2f,  -0.3f,  0.0f,  1.0f), red, tcu::Vec4( 0.2f, -0.3f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.0f, -offset, -2.0f,  1.0f), blue},

				// two vertices clipped to edges
				{tcu::Vec4( 0.6f, -1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f, -0.6f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.6f, -0.6f,  0.0f,  1.0f), blue},
				{tcu::Vec4( 0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f,  0.6f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.6f,  0.6f,  0.0f,  1.0f), blue},
				{tcu::Vec4(-0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4(-1.2f,  0.6f,  0.0f,  1.0f), yellow, tcu::Vec4(-0.6f,  0.6f,  0.0f,  1.0f), blue},
				{tcu::Vec4(-0.6f, -1.2f,  0.0f,  1.0f), red, tcu::Vec4(-1.2f, -0.6f,  0.0f,  1.0f), yellow, tcu::Vec4(-0.6f, -0.6f,  0.0f,  1.0f), blue},
			};

			addChild(new TriangleAttributeCase(m_context, "multiple_3",					"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_WHOLE));
			addChild(new TriangleAttributeCase(m_context, "multiple_3_viewport_center",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CENTER));
			addChild(new TriangleAttributeCase(m_context, "multiple_3_viewport_corner",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CORNER));
		}

		{
			const TriangleAttributeCase::TriangleData polys[] =
			{
				// one vertex clipped to z
				{tcu::Vec4(0.3f,  0.2f,  0.0f,  1.0f), red, tcu::Vec4( 0.3f, -0.2f,  0.0f,  1.0f), yellow, tcu::Vec4( offset, 0.0f,  2.0f,  1.0f), blue},

				// two vertices clipped to edges
				{tcu::Vec4( 0.6f, -1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f, -0.6f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.6f, -0.6f,  0.0f,  1.0f), blue},
				{tcu::Vec4( 0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f,  0.6f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.6f,  0.6f,  0.0f,  1.0f), blue},
				{tcu::Vec4(-0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4(-1.2f,  0.6f,  0.0f,  1.0f), yellow, tcu::Vec4(-0.6f,  0.6f,  0.0f,  1.0f), blue},
				{tcu::Vec4(-0.6f, -1.2f,  0.0f,  1.0f), red, tcu::Vec4(-1.2f, -0.6f,  0.0f,  1.0f), yellow, tcu::Vec4(-0.6f, -0.6f,  0.0f,  1.0f), blue},
			};

			addChild(new TriangleAttributeCase(m_context, "multiple_4",					"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_WHOLE));
			addChild(new TriangleAttributeCase(m_context, "multiple_4_viewport_center",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CENTER));
			addChild(new TriangleAttributeCase(m_context, "multiple_4_viewport_corner",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CORNER));
		}

		{
			const TriangleAttributeCase::TriangleData polys[] =
			{
				// one vertex clipped to z
				{tcu::Vec4(-0.3f,  0.2f,  0.0f,  1.0f), red, tcu::Vec4(-0.3f, -0.2f,  0.0f,  1.0f), yellow, tcu::Vec4(-offset, 0.0f,  2.0f,  1.0f), blue},

				// two vertices clipped to edges
				{tcu::Vec4( 0.6f, -1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f, -0.6f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.6f, -0.6f,  0.0f,  1.0f), blue},
				{tcu::Vec4( 0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f,  0.6f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.6f,  0.6f,  0.0f,  1.0f), blue},
				{tcu::Vec4(-0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4(-1.2f,  0.6f,  0.0f,  1.0f), yellow, tcu::Vec4(-0.6f,  0.6f,  0.0f,  1.0f), blue},
				{tcu::Vec4(-0.6f, -1.2f,  0.0f,  1.0f), red, tcu::Vec4(-1.2f, -0.6f,  0.0f,  1.0f), yellow, tcu::Vec4(-0.6f, -0.6f,  0.0f,  1.0f), blue},
			};

			addChild(new TriangleAttributeCase(m_context, "multiple_5",					"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_WHOLE));
			addChild(new TriangleAttributeCase(m_context, "multiple_5_viewport_center",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CENTER));
			addChild(new TriangleAttributeCase(m_context, "multiple_5_viewport_corner",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CORNER));
		}

		{
			const TriangleAttributeCase::TriangleData polys[] =
			{
				// one vertex clipped to z
				{tcu::Vec4(-0.2f,  0.3f,  0.0f,  1.0f), red, tcu::Vec4( 0.2f, 0.3f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.0f, offset,  2.0f,  1.0f), blue},

				// two vertices clipped to edges
				{tcu::Vec4( 0.6f, -1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f, -0.6f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.6f, -0.6f,  0.0f,  1.0f), blue},
				{tcu::Vec4( 0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f,  0.6f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.6f,  0.6f,  0.0f,  1.0f), blue},
				{tcu::Vec4(-0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4(-1.2f,  0.6f,  0.0f,  1.0f), yellow, tcu::Vec4(-0.6f,  0.6f,  0.0f,  1.0f), blue},
				{tcu::Vec4(-0.6f, -1.2f,  0.0f,  1.0f), red, tcu::Vec4(-1.2f, -0.6f,  0.0f,  1.0f), yellow, tcu::Vec4(-0.6f, -0.6f,  0.0f,  1.0f), blue},
			};

			addChild(new TriangleAttributeCase(m_context, "multiple_6",					"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_WHOLE));
			addChild(new TriangleAttributeCase(m_context, "multiple_6_viewport_center",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CENTER));
			addChild(new TriangleAttributeCase(m_context, "multiple_6_viewport_corner",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CORNER));
		}

		{
			const TriangleAttributeCase::TriangleData polys[] =
			{
				// two vertices clipped to edges
				{tcu::Vec4( 0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f,  0.6f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.6f,  0.6f,  0.0f,  1.0f), blue},

				// two vertices clipped to edges
				{tcu::Vec4( 0.6f, -1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f, -0.6f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.6f, -0.6f,  0.0f,  1.0f), blue},
				{tcu::Vec4( 0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4( 1.2f,  0.6f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.6f,  0.6f,  0.0f,  1.0f), blue},
				{tcu::Vec4(-0.6f,  1.2f,  0.0f,  1.0f), red, tcu::Vec4(-1.2f,  0.6f,  0.0f,  1.0f), yellow, tcu::Vec4(-0.6f,  0.6f,  0.0f,  1.0f), blue},
				{tcu::Vec4(-0.6f, -1.2f,  0.0f,  1.0f), red, tcu::Vec4(-1.2f, -0.6f,  0.0f,  1.0f), yellow, tcu::Vec4(-0.6f, -0.6f,  0.0f,  1.0f), blue},
			};

			addChild(new TriangleAttributeCase(m_context, "multiple_7",					"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_WHOLE));
			addChild(new TriangleAttributeCase(m_context, "multiple_7_viewport_center",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CENTER));
			addChild(new TriangleAttributeCase(m_context, "multiple_7_viewport_corner",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CORNER));
		}

		{
			const TriangleAttributeCase::TriangleData polys[] =
			{
				// one vertex clipped to z
				{tcu::Vec4(-0.2f,  -0.3f,  0.0f,  1.0f), red, tcu::Vec4( 0.2f, -0.3f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.0f, -offset,  2.0f,  1.0f), blue},

				// fill
				{tcu::Vec4( -1.0f, -1.0f,  0.0f,  1.0f), white, tcu::Vec4( 1.0f, -1.0f,  0.0f,  1.0f), white, tcu::Vec4( -1.0f, 1.0f,  0.0f,  1.0f), white},
				{tcu::Vec4( -1.0f,  1.0f,  0.0f,  1.0f), blue,	tcu::Vec4( 1.0f, -1.0f,  0.0f,  1.0f), blue, tcu::Vec4(  1.0f, 1.0f,  0.0f,  1.0f), blue},
			};

			addChild(new TriangleAttributeCase(m_context, "multiple_8",					"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_WHOLE));
			addChild(new TriangleAttributeCase(m_context, "multiple_8_viewport_center",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CENTER));
			addChild(new TriangleAttributeCase(m_context, "multiple_8_viewport_corner",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CORNER));
		}

		{
			const TriangleAttributeCase::TriangleData polys[] =
			{
				// one vertex clipped to z
				{tcu::Vec4(-0.2f,  -0.3f,  0.0f,  1.0f), red, tcu::Vec4( 0.2f, -0.3f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.0f, -offset,  2.0f,  1.0f), blue},

				// fill
				{tcu::Vec4( -1.0f,  1.0f,  0.0f,  1.0f), red,  tcu::Vec4( 1.0f, -1.0f,  0.0f,  1.0f), red,  tcu::Vec4(  1.0f, 1.0f,  0.0f,  1.0f), red},
				{tcu::Vec4( -1.0f, -1.0f,  0.0f,  1.0f), blue, tcu::Vec4( 1.0f, -1.0f,  0.0f,  1.0f), blue, tcu::Vec4( -1.0f, 1.0f,  0.0f,  1.0f), blue},
			};

			addChild(new TriangleAttributeCase(m_context, "multiple_9",					"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_WHOLE));
			addChild(new TriangleAttributeCase(m_context, "multiple_9_viewport_center",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CENTER));
			addChild(new TriangleAttributeCase(m_context, "multiple_9_viewport_corner",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CORNER));
		}

		{
			const TriangleAttributeCase::TriangleData polys[] =
			{
				// one vertex clipped to z
				{tcu::Vec4(-0.2f,  -0.3f,  0.0f,  1.0f), red, tcu::Vec4( 0.2f, -0.3f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.0f, -offset,  2.0f,  1.0f), blue},

				// fill
				{tcu::Vec4( -1.0f, -1.0f,  0.0f,  1.0f), white, tcu::Vec4( 1.0f, -1.0f,  0.0f,  1.0f), white, tcu::Vec4( -1.0f, 1.0f,  0.0f,  1.0f), white},
				{tcu::Vec4( -1.0f,  1.0f,  0.0f,  1.0f), red,   tcu::Vec4( 1.0f, -1.0f,  0.0f,  1.0f), red,   tcu::Vec4(  1.0f, 1.0f,  0.0f,  1.0f), red},
				{tcu::Vec4( -1.0f, -1.0f,  0.0f,  1.0f), blue,  tcu::Vec4( 1.0f, -1.0f,  0.0f,  1.0f), blue,  tcu::Vec4( -1.0f, 1.0f,  0.0f,  1.0f), blue},
			};

			addChild(new TriangleAttributeCase(m_context, "multiple_10",					"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_WHOLE));
			addChild(new TriangleAttributeCase(m_context, "multiple_10_viewport_center",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CENTER));
			addChild(new TriangleAttributeCase(m_context, "multiple_10_viewport_corner",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CORNER));
		}

		{
			const TriangleAttributeCase::TriangleData polys[] =
			{
				// one vertex clipped to z
				{tcu::Vec4(-0.2f,  -0.3f,  0.0f,  1.0f), red, tcu::Vec4( 0.2f, -0.3f,  0.0f,  1.0f), yellow, tcu::Vec4( 0.0f, -offset,  2.0f,  1.0f), blue},

				// fill
				{tcu::Vec4( -1.0f, -1.0f,  0.0f,  1.0f), white,  tcu::Vec4( 1.0f, -1.0f,  0.0f,  1.0f), white,  tcu::Vec4( -1.0f, 1.0f,  0.0f,  1.0f), white},
				{tcu::Vec4( -1.0f,  1.0f,  0.0f,  1.0f), red,    tcu::Vec4( 1.0f, -1.0f,  0.0f,  1.0f), red,    tcu::Vec4(  1.0f, 1.0f,  0.0f,  1.0f), red},
				{tcu::Vec4( -1.0f, -1.0f,  0.0f,  1.0f), blue,   tcu::Vec4( 1.0f, -1.0f,  0.0f,  1.0f), blue,   tcu::Vec4( -1.0f, 1.0f,  0.0f,  1.0f), blue},
				{tcu::Vec4( -1.0f,  1.0f,  0.0f,  1.0f), yellow, tcu::Vec4( 1.0f, -1.0f,  0.0f,  1.0f), yellow, tcu::Vec4(  1.0f, 1.0f,  0.0f,  1.0f), yellow},
			};

			addChild(new TriangleAttributeCase(m_context, "multiple_11",					"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_WHOLE));
			addChild(new TriangleAttributeCase(m_context, "multiple_11_viewport_center",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CENTER));
			addChild(new TriangleAttributeCase(m_context, "multiple_11_viewport_corner",	"polygon clipping",		DE_ARRAY_BEGIN(polys),		DE_ARRAY_END(polys),		VIEWPORT_CORNER));
		}
	}
}

class PolyEdgesTestGroup : public TestCaseGroup
{
public:
			PolyEdgesTestGroup	(Context& context);

	void	init				(void);
};

PolyEdgesTestGroup::PolyEdgesTestGroup (Context& context)
	: TestCaseGroup(context, "polygon_edge", "Polygon clipping edge tests")
{
}

void PolyEdgesTestGroup::init (void)
{
	// Quads via origin
	const struct Quad
	{
		tcu::Vec3 d1; // tangent
		tcu::Vec3 d2; // bi-tangent
	} quads[] =
	{
		{ tcu::Vec3( 1, 1, 1),	tcu::Vec3( 1,   -1, 1) },
		{ tcu::Vec3( 1, 1, 1),	tcu::Vec3(-1, 1.1f, 1) },
		{ tcu::Vec3( 1, 1, 0),	tcu::Vec3(-1,    1, 0) },
		{ tcu::Vec3( 0, 1, 0),	tcu::Vec3( 1,    0, 0) },
		{ tcu::Vec3( 0, 1, 0),	tcu::Vec3( 1, 0.1f, 0) },
	};

	// Quad near edge
	const struct EdgeQuad
	{
		tcu::Vec3 d1;		// tangent
		tcu::Vec3 d2;		// bi-tangent
		tcu::Vec3 center;	// center
	} edgeQuads[] =
	{
		{ tcu::Vec3( 1,     0.01f, 0    ),	tcu::Vec3( 0,      0.01f,  0),  tcu::Vec3( 0,     0.99f, 0    ) }, // edge near x-plane
		{ tcu::Vec3( 0.01f, 1,     0    ),	tcu::Vec3( 0.01f,  0,      0),  tcu::Vec3( 0.99f, 0,     0    ) }, // edge near y-plane
		{ tcu::Vec3( 1,     1,     0.01f),	tcu::Vec3( 0.01f,  -0.01f, 0),  tcu::Vec3( 0,     0,     0.99f) }, // edge near z-plane
	};

	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(quads); ++ndx)
		addChild(new QuadFillTest(m_context, (std::string("quad_at_origin_") + de::toString(ndx)).c_str(), "polygon edge clipping", VIEWPORT_CENTER, quads[ndx].d1, quads[ndx].d2));
	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(edgeQuads); ++ndx)
		addChild(new QuadFillTest(m_context, (std::string("quad_near_edge_") + de::toString(ndx)).c_str(), "polygon edge clipping", VIEWPORT_CENTER, edgeQuads[ndx].d1, edgeQuads[ndx].d2, edgeQuads[ndx].center));

	// Polyfan
	addChild(new TriangleFanFillTest(m_context, "poly_fan", "polygon edge clipping", VIEWPORT_CENTER));
}

class PolyVertexClipTestGroup : public TestCaseGroup
{
public:
			PolyVertexClipTestGroup	(Context& context);

	void	init					(void);
};

PolyVertexClipTestGroup::PolyVertexClipTestGroup (Context& context)
	: TestCaseGroup(context, "triangle_vertex", "Clip n vertices")
{
}

void PolyVertexClipTestGroup::init (void)
{
	const float far = 30000.0f;
	const tcu::IVec3 outside[] =
	{
		// outside one clipping plane
		tcu::IVec3(-1,  0,  0),
		tcu::IVec3( 1,  0,  0),
		tcu::IVec3( 0,  1,  0),
		tcu::IVec3( 0, -1,  0),
		tcu::IVec3( 0,  0,  1),
		tcu::IVec3( 0,  0, -1),

		// outside two clipping planes
		tcu::IVec3(-1, -1,  0),
		tcu::IVec3( 1, -1,  0),
		tcu::IVec3( 1,  1,  0),
		tcu::IVec3(-1,  1,  0),

		tcu::IVec3(-1,  0, -1),
		tcu::IVec3( 1,  0, -1),
		tcu::IVec3( 1,  0,  1),
		tcu::IVec3(-1,  0,  1),

		tcu::IVec3( 0, -1, -1),
		tcu::IVec3( 0,  1, -1),
		tcu::IVec3( 0,  1,  1),
		tcu::IVec3( 0, -1,  1),

		// outside three clipping planes
		tcu::IVec3(-1, -1,  1),
		tcu::IVec3( 1, -1,  1),
		tcu::IVec3( 1,  1,  1),
		tcu::IVec3(-1,  1,  1),

		tcu::IVec3(-1, -1, -1),
		tcu::IVec3( 1, -1, -1),
		tcu::IVec3( 1,  1, -1),
		tcu::IVec3(-1,  1, -1),
	};

	de::Random rnd(0xabcdef);

	TestCaseGroup* clipOne		= new TestCaseGroup(m_context, "clip_one",		"Clip one vertex");
	TestCaseGroup* clipTwo		= new TestCaseGroup(m_context, "clip_two",		"Clip two vertices");
	TestCaseGroup* clipThree	= new TestCaseGroup(m_context, "clip_three",	"Clip three vertices");

	addChild(clipOne);
	addChild(clipTwo);
	addChild(clipThree);

	// Test 1 point clipped
	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(outside); ++ndx)
	{
		const float		w0		= rnd.getFloat(0.2f, 16.0f);
		const float		w1		= rnd.getFloat(0.2f, 16.0f);
		const float		w2		= rnd.getFloat(0.2f, 16.0f);
		const tcu::Vec4 white	= tcu::Vec4(    1,	    1,	1,	1);
		const tcu::Vec3 r0		= tcu::Vec3( 0.2f,	 0.3f,	0);
		const tcu::Vec3 r1		= tcu::Vec3(-0.3f,	-0.4f,	0);
		const tcu::Vec3 r2		= IVec3ToVec3(outside[ndx]) * far;
		const tcu::Vec4 p0		= tcu::Vec4(r0.x() * w0, r0.y() * w0, r0.z() * w0, w0);
		const tcu::Vec4 p1		= tcu::Vec4(r1.x() * w1, r1.y() * w1, r1.z() * w1, w1);
		const tcu::Vec4 p2		= tcu::Vec4(r2.x() * w2, r2.y() * w2, r2.z() * w2, w2);

		const std::string name	= std::string("clip") +
			(outside[ndx].x() > 0 ? "_pos_x" : (outside[ndx].x() < 0 ? "_neg_x" : "")) +
			(outside[ndx].y() > 0 ? "_pos_y" : (outside[ndx].y() < 0 ? "_neg_y" : "")) +
			(outside[ndx].z() > 0 ? "_pos_z" : (outside[ndx].z() < 0 ? "_neg_z" : ""));

		const TriangleCase::TriangleData triangle =	{p0, white, p1, white, p2, white};

		// don't try to test with degenerate (or almost degenerate) triangles
		if (outside[ndx].x() == 0 && outside[ndx].y() == 0)
			continue;

		clipOne->addChild(new TriangleCase(m_context, name.c_str(), "clip one vertex", &triangle, &triangle + 1, VIEWPORT_CENTER));
	}

	// Special triangles for "clip_z" cases, default triangles is not good, since it has very small visible area => problems with MSAA
	{
		const tcu::Vec4 white = tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f);

		const TriangleCase::TriangleData posZTriangle =
		{
			tcu::Vec4( 0.0f, -0.7f, -0.9f, 1.0f), white,
			tcu::Vec4( 0.8f,  0.0f, -0.7f, 1.0f), white,
			tcu::Vec4(-0.9f,  0.9f,  3.0f, 1.0f), white
		};
		const TriangleCase::TriangleData negZTriangle =
		{
			tcu::Vec4( 0.0f, -0.7f,  0.9f, 1.0f), white,
			tcu::Vec4( 0.4f,  0.0f,  0.7f, 1.0f), white,
			tcu::Vec4(-0.9f,  0.9f, -3.0f, 1.0f), white
		};

		clipOne->addChild(new TriangleCase(m_context, "clip_pos_z", "clip one vertex", &posZTriangle, &posZTriangle + 1, VIEWPORT_CENTER));
		clipOne->addChild(new TriangleCase(m_context, "clip_neg_z", "clip one vertex", &negZTriangle, &negZTriangle + 1, VIEWPORT_CENTER));
	}

	// Test 2 points clipped
	for (int ndx1 = 0; ndx1 < DE_LENGTH_OF_ARRAY(outside); ++ndx1)
	for (int ndx2 = ndx1 + 1; ndx2 < DE_LENGTH_OF_ARRAY(outside); ++ndx2)
	{
		const float		w0		= rnd.getFloat(0.2f, 16.0f);
		const float		w1		= rnd.getFloat(0.2f, 16.0f);
		const float		w2		= rnd.getFloat(0.2f, 16.0f);
		const tcu::Vec4 white	= tcu::Vec4(    1,	    1,	1,	1);
		const tcu::Vec3 r0		= tcu::Vec3( 0.2f,	 0.3f,	0);
		const tcu::IVec3 r1		= outside[ndx1];
		const tcu::IVec3 r2		= outside[ndx2];
		const tcu::Vec4 p0		= tcu::Vec4(r0.x() * w0, r0.y() * w0, r0.z() * w0, w0);
		const tcu::Vec4 p1		= tcu::Vec4(float(r1.x()) * far * w1, float(r1.y()) * far * w1, float(r1.z()) * far * w1, w1);
		const tcu::Vec4 p2		= tcu::Vec4(float(r2.x()) * far * w2, float(r2.y()) * far * w2, float(r2.z()) * far * w2, w2);

		const std::string name	= std::string("clip") +
			(outside[ndx1].x() > 0 ? "_pos_x" : (outside[ndx1].x() < 0 ? "_neg_x" : "")) +
			(outside[ndx1].y() > 0 ? "_pos_y" : (outside[ndx1].y() < 0 ? "_neg_y" : "")) +
			(outside[ndx1].z() > 0 ? "_pos_z" : (outside[ndx1].z() < 0 ? "_neg_z" : "")) +
			"_and" +
			(outside[ndx2].x() > 0 ? "_pos_x" : (outside[ndx2].x() < 0 ? "_neg_x" : "")) +
			(outside[ndx2].y() > 0 ? "_pos_y" : (outside[ndx2].y() < 0 ? "_neg_y" : "")) +
			(outside[ndx2].z() > 0 ? "_pos_z" : (outside[ndx2].z() < 0 ? "_neg_z" : ""));

		const TriangleCase::TriangleData triangle =	{p0, white, p1, white, p2, white};

		if (twoPointClippedTriangleInvisible(r0, r1, r2))
			continue;

		clipTwo->addChild(new TriangleCase(m_context, name.c_str(), "clip two vertices", &triangle, &triangle + 1, VIEWPORT_CENTER));
	}

	// Test 3 points clipped
	for (int ndx1 = 0; ndx1 < DE_LENGTH_OF_ARRAY(outside); ++ndx1)
	for (int ndx2 = ndx1 + 1; ndx2 < DE_LENGTH_OF_ARRAY(outside); ++ndx2)
	for (int ndx3 = ndx2 + 1; ndx3 < DE_LENGTH_OF_ARRAY(outside); ++ndx3)
	{
		const float		w0		= rnd.getFloat(0.2f, 16.0f);
		const float		w1		= rnd.getFloat(0.2f, 16.0f);
		const float		w2		= rnd.getFloat(0.2f, 16.0f);
		const tcu::Vec4 white	= tcu::Vec4(1, 1, 1, 1);
		const tcu::IVec3 r0		= outside[ndx1];
		const tcu::IVec3 r1		= outside[ndx2];
		const tcu::IVec3 r2		= outside[ndx3];
		const tcu::Vec4 p0		= tcu::Vec4(float(r0.x()) * far * w0, float(r0.y()) * far * w0, float(r0.z()) * far * w0, w0);
		const tcu::Vec4 p1		= tcu::Vec4(float(r1.x()) * far * w1, float(r1.y()) * far * w1, float(r1.z()) * far * w1, w1);
		const tcu::Vec4 p2		= tcu::Vec4(float(r2.x()) * far * w2, float(r2.y()) * far * w2, float(r2.z()) * far * w2, w2);

		// ignore cases where polygon is along xz or yz planes
		if (pointsOnLine(r0.swizzle(0, 1), r1.swizzle(0, 1), r2.swizzle(0, 1)))
			continue;

		// triangle is visible only if it intersects the origin
		if (pointOnTriangle(tcu::IVec3(0, 0, 0), r0, r1, r2))
		{
			const TriangleCase::TriangleData triangle =	{p0, white, p1, white, p2, white};
			const std::string name	= std::string("clip") +
				(outside[ndx1].x() > 0 ? "_pos_x" : (outside[ndx1].x() < 0 ? "_neg_x" : "")) +
				(outside[ndx1].y() > 0 ? "_pos_y" : (outside[ndx1].y() < 0 ? "_neg_y" : "")) +
				(outside[ndx1].z() > 0 ? "_pos_z" : (outside[ndx1].z() < 0 ? "_neg_z" : "")) +
				"_and" +
				(outside[ndx2].x() > 0 ? "_pos_x" : (outside[ndx2].x() < 0 ? "_neg_x" : "")) +
				(outside[ndx2].y() > 0 ? "_pos_y" : (outside[ndx2].y() < 0 ? "_neg_y" : "")) +
				(outside[ndx2].z() > 0 ? "_pos_z" : (outside[ndx2].z() < 0 ? "_neg_z" : "")) +
				"_and" +
				(outside[ndx3].x() > 0 ? "_pos_x" : (outside[ndx3].x() < 0 ? "_neg_x" : "")) +
				(outside[ndx3].y() > 0 ? "_pos_y" : (outside[ndx3].y() < 0 ? "_neg_y" : "")) +
				(outside[ndx3].z() > 0 ? "_pos_z" : (outside[ndx3].z() < 0 ? "_neg_z" : ""));

			clipThree->addChild(new TriangleCase(m_context, name.c_str(), "clip three vertices", &triangle, &triangle + 1, VIEWPORT_CENTER));
		}
	}
}

} // anonymous

ClippingTests::ClippingTests (Context& context)
	: TestCaseGroup(context, "clipping", "Clipping tests")
{
}

ClippingTests::~ClippingTests (void)
{
}

void ClippingTests::init (void)
{
	addChild(new PointsTestGroup		(m_context));
	addChild(new LinesTestGroup			(m_context));
	addChild(new PolysTestGroup			(m_context));
	addChild(new PolyEdgesTestGroup		(m_context));
	addChild(new PolyVertexClipTestGroup(m_context));
}

} // Functional
} // gles3
} // deqp
